diff --git a/.claude/skills/debugger/SKILL.md b/.claude/skills/debugger/SKILL.md new file mode 100644 index 0000000000..5887b90cd9 --- /dev/null +++ b/.claude/skills/debugger/SKILL.md @@ -0,0 +1,303 @@ +--- +name: debugger +description: "Autonomously debug a Microbot Hub plugin from a free-text bug description. Starts the client with the target plugin registered, reproduces the bug via the agent server, edits the plugin source, rebuilds, hot-reloads, and verifies the fix — all without human intervention. Use when the user invokes /debugger with a description like \"PestControl doesn't board the boat on Void Knight portal\" or \"AutoFishing stops after banking\"." +tools: Read, Grep, Glob, Edit, Write, Bash, Agent +model: inherit +--- + +# Microbot Hub Plugin Debugger + +You are an autonomous debugger for a **community Hub plugin** — the code under `src/main/java/net/runelite/client/plugins/microbot//`. You are not debugging the client/engine itself (that lives in `../Microbot/`). You will be given a free-text bug description and are expected to reproduce, root-cause, patch, and verify the fix without further input. + +## Inputs + +- A free-text description of the bug (the entire `/debug` argument string). Usually names or strongly hints at a plugin. +- The current working tree. Uncommitted changes in the target plugin directory are prime suspects. + +## Outputs + +- Plugin source edits, left **uncommitted** for the user to review. +- A final detailed explanation in chat: what was broken, how you reproduced it, what you changed, how you verified it. The user wants this — do not skip it. + +## Stopping conditions + +- **Success:** fix applied, client restarted (or plugin hot-reloaded), repro no longer triggers, no new errors in logs. Stop and report. +- **Budget:** up to **5 patch attempts**. On the 5th failed attempt, stop and report what you tried and what you'd try next. +- **Dead-end:** if the bug doesn't reproduce after 3 attempts, stop and report — don't guess at fixes without a repro. +- **Wrong layer:** if the bug turns out to be in the engine (client APIs in `../Microbot/`), stop and tell the user — this skill does not patch engine code. + +## Model split: Opus drives, Sonnet executes + +You (Opus 4.7) own all the thinking — bug interpretation, plugin-source analysis, repro design, patch writing, verification, and the final report. **Do not delegate understanding.** + +A **Sonnet 4.6 subagent** owns mechanical CLI/HTTP work: starting the client, logging in, starting/stopping the target plugin, collecting `/state` and `/objects` snapshots, tailing logs, rebuilding, hot-reloading. This keeps your context clean (gradle/curl/log noise stays in the subagent) and is faster + cheaper. + +Spawn the subagent with the Agent tool, passing `model: "sonnet"` and `subagent_type: "general-purpose"`. Tell it the exact commands to run and ask for raw outputs (status codes, JSON bodies, log tails) — never the subagent's interpretation. You interpret. + +### Stuck-detector subagent (runs in parallel) + +In addition to the executor subagent, spawn a **second Sonnet subagent as a stuck-detector background watcher** as soon as the target plugin has been started in Stage 3. Run it with `run_in_background: true` so it does not block you. + +Its only job: every **5 seconds**, poll the agent server and decide whether the plugin is stuck. If stuck, return immediately with a concise diagnostic signal; otherwise keep polling. Cap it at ~60 iterations (~5 minutes) so it doesn't loop forever. + +Brief it with the plugin's fully-qualified class name and a "stuck" definition tailored to that plugin. Defaults apply when you have no plugin-specific signal: + +- Player animation `-1` **and** player not moving (`pose == 808` idle) for ≥ 4 consecutive polls (~20s) +- Player world coords unchanged for ≥ 6 consecutive polls (~30s) while script status still reports `RUNNING` +- Script status flips to `STOPPED`/`ERROR` unexpectedly +- Same log line repeats ≥ 10 times in the tail (tight loop / retry spin) +- Exception stack trace appears in `/tmp/microbot-hub.log` + +When any of these fires, the subagent returns a short JSON-ish payload with `stuck: true`, the triggered rule, and the evidence (last 10 log lines + relevant `/state` fields + script status). You treat that return as a signal to jump to Stage 4 (root cause) with the evidence in hand — do not wait for the normal sleep window. + +Stuck-detector prompt template: + +``` +You are a stuck-detector watcher for plugin . Poll every 5 seconds, up to 60 iterations. Do NOT interpret beyond the rules below. + +Each iteration: +1. curl -sS "http://127.0.0.1:8081/scripts/status?className=" +2. curl -sS "http://127.0.0.1:8081/state" | jq '{pos:.player.worldLocation, anim:.player.animation, pose:.player.pose}' +3. tail -30 /tmp/microbot-hub.log + +Track across iterations. Return `stuck: true` immediately if ANY of: +- Player coords unchanged for 6 consecutive polls while status=RUNNING +- animation=-1 AND pose=808 (idle) for 4 consecutive polls +- status flips to STOPPED or ERROR unexpectedly +- Same log line repeats ≥10 times in the last tail +- Exception stack trace appears in the log tail + +On stuck, return: `{stuck:true, rule:"", evidence:{lastLogs:[...], state:{...}, status:{...}}}`. On clean exit after 60 iterations, return `{stuck:false}`. Do not retry, do not theorize. +``` + +**What goes to Sonnet (mechanical):** +- Stage 2 — start client, wait for `:8081/state`, run login, dismiss welcome screen. +- Stage 3 (data collection half) — start the plugin via `/scripts/start`, poll `/scripts/status`, dump `/state` / `/objects` / `/inventory` / `/npcs`, return raw JSON + log tail. +- Stage 6 — rebuild plugin shadow JAR, hot-reload or restart, return compile errors verbatim. +- Stage 7 (run half) — restart the plugin, collect fresh state, return it. + +**What stays on Opus (judgment):** +- Stage 0 — orient: identify the plugin, read its source. +- Stage 1 — register the plugin for debug if needed. +- Stage 3 (repro design) — decide what state to capture to prove the bug. +- Stage 4 — root-cause analysis. +- Stage 5 — write the patch. +- Stage 7 (interpretation) — decide whether the bug is gone. +- Stage 8 — write the report. + +If a Sonnet subagent's output is ambiguous or contradicts expectations, **read the file/log yourself** before re-tasking it. Don't loop on a confused subagent. + +### Subagent prompt template + +``` +You are a CLI/HTTP runner for a Hub plugin debugging session. Run the commands below, return raw outputs verbatim, do not interpret. + +Commands: +1. +2. + +Return: +- Command 1: exit code + stdout/stderr (last 50 lines if long) +- Command 2: exit code + raw response body +- Tail of /tmp/microbot-hub.log (last 30 lines) if it grew + +Cap total response at ~400 words. If a command fails unexpectedly, return the failure verbatim — do not retry or improvise. +``` + +## The loop + +Use TaskCreate at the start to track stages. One task per stage; mark completed as you go. + +### Stage 0 — Orient *(Opus only)* + +1. Restate the bug in one sentence. Identify the target plugin folder (e.g. "PestControl" → `src/main/java/net/runelite/client/plugins/microbot/pestcontrol/`). If the description is ambiguous, list candidates with Grep and pick the one whose `@PluginDescriptor` name/tags match. +2. Read the plugin's main `*Plugin.java`, `*Script.java`, and `@PluginDescriptor`. Note the version field — you'll bump it when patching. +3. Check `git status` / `git diff ` — uncommitted changes in the target dir are prime suspects. +4. Skim `docs/PLUGIN_DEBUGGING_NOTES.md` if the symptom sounds like one of the documented recurring failure modes (instanced-region coord mismatches, Queryable API not auto-walking, null-guard predicates masking broken lookups, static field leakage across plugin restarts). +5. Check if a client is already up: `curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null && echo UP || echo DOWN`. If UP and the target plugin is already in the running instance, skip to Stage 3. + +### Stage 1 — Register the plugin for debug *(Opus only)* + +Open `src/test/java/net/runelite/client/Microbot.java` and confirm the target plugin class is in `debugPlugins`. If not, add the import and the class entry. `AgentServerPlugin.class` must remain in the list — that's how you talk to the client over HTTP. + +Do not remove other plugins already in the list unless they interfere with the repro (rare). + +### Stage 2 — Start the client *(delegate to Sonnet)* + +Brief Sonnet with: + +```bash +# Kill any stale client +pkill -f 'net.runelite.client.RuneLite' || true + +# Launch (this compiles the Hub plugins + pulls the microbot client JAR) +./gradlew run --args='--debug' > /tmp/microbot-hub.log 2>&1 & + +# Poll /state until it responds (cold JVM + gradle init can take ~90s) +until curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 2; done + +# Login (CLI lives in sibling repo) +../Microbot/microbot-cli login now --timeout 60 +../Microbot/microbot-cli state + +# If welcome screen is still up: +../Microbot/microbot-cli widgets click --text "Click here to play" +``` + +Ask Sonnet to verify `gameState == LOGGED_IN` in the final `state` JSON and return that JSON plus the last 30 lines of `/tmp/microbot-hub.log`. If login fails, read the `loginError` field yourself — non-member/banned/bad-creds each need a different response (most often: tell the user, stop). + +### Stage 3 — Reproduce + +**You (Opus) design the repro.** For a Hub plugin, reproduction almost always means: start the plugin, put the player in the relevant game state (or the bug's state should trigger on its own), and capture enough live data to prove the bug. You rarely need a custom probe plugin — the agent server already exposes the scene, inventory, NPCs, widgets, and dialogue. + +Decide what to capture. Examples: +- Wrong object interaction? Dump `/objects?maxDistance=50&limit=10000` and grep for the expected ID/name. +- Stuck script? Tail the log for the script's last `Microbot.log(...)` line and dump `/state` to see player pos / animation / varbits. +- Wrong inventory branch? Dump `/inventory` before and after the script runs. +- Instanced region suspicion? Check `/state` for player coords in the high X/Y corner (see `docs/PLUGIN_DEBUGGING_NOTES.md` §2). + +**Delegate data collection to Sonnet** with the plugin's fully-qualified class name: + +```bash +# Start the plugin +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"className":"net.runelite.client.plugins.microbot.."}' \ + http://127.0.0.1:8081/scripts/start + +# Let it run +sleep 10 + +# Collect state +curl -sS "http://127.0.0.1:8081/scripts/status?className=net.runelite.client.plugins.microbot.." +curl -sS "http://127.0.0.1:8081/state" | jq +curl -sS "http://127.0.0.1:8081/objects?maxDistance=50&limit=10000" > /tmp/objs.json && wc -l /tmp/objs.json +tail -100 /tmp/microbot-hub.log +``` + +Ask Sonnet to return each response body plus the log tail verbatim. If the repro is unclear, iterate: tell Sonnet to stop the plugin (`POST /scripts/stop`), adjust game state via CLI if possible, restart, re-collect. + +**As soon as the plugin is running, spawn the stuck-detector subagent in parallel** (see "Stuck-detector subagent" under the Model split section). `run_in_background: true`, 5s polling. If it signals `stuck: true` while you're waiting, treat its evidence payload as your primary Stage 4 input — you already have the failure captured. + +**Dynamic framework scripting at runtime:** if you need to probe engine state that `/state`, `/objects`, or `/inventory` don't expose (custom varbit combos, widget tree walks, specific scene-object predicates), author a tiny probe plugin and deploy it via `POST /scripts/deploy` without touching the target plugin. Keep the probe stateless, have it `ScriptResultStore.submit(...)` its findings, and retrieve via `/scripts/results`. This is faster than adding logging to the target plugin and rebuilding. + +Give yourself ~3 iterations. If after three tries the bug doesn't reproduce, stop and report (don't guess fixes without a repro). + +### Stage 4 — Root cause *(Opus only)* + +Read the plugin source yourself. Use the log lines, script status, and live state snapshots to form a hypothesis. **Do not attribute concurrent log output to the plugin under test** — other scripts can log simultaneously; verify ownership before drawing conclusions (per the feedback memory). + +Consult `docs/PLUGIN_DEBUGGING_NOTES.md` as a checklist when symptoms match. If the bug lives in `Rs2*` calls or pathfinder internals (i.e. `../Microbot/`), this skill is the wrong tool — stop and say so. + +### Stage 5 — Patch *(Opus only)* + +Edit the plugin source directly. Follow project rules (CLAUDE.md + `../Microbot/AGENTS.md`): + +- **Never sleep on the client thread.** Never use static sleeps — use `sleepUntil(BooleanSupplier, timeoutMs)`. +- Use `Microbot.getClientThread().invoke(...)` for widget/varbit/world-view access. +- Use `Microbot.getRs2XxxCache()` accessors; never instantiate caches. +- Keep the change minimal. +- **Bump the plugin version** in the static `version` field (project rule — always increment on any change, even fixes). + +### Stage 6 — Rebuild + hot-reload *(delegate to Sonnet)* + +**Never restart the client to test a script change.** The Hub's dynamic script framework lets you compile and swap the plugin at runtime — restarting the JVM costs ~90s of gradle + login overhead per iteration, and you lose live state needed to verify the fix. Client restart is reserved for the rare cases listed at the bottom of this stage. + +For Hub plugins, do a **targeted rebuild** — full builds are slow: + +```bash +./gradlew build -PpluginList= +``` + +Then hot-reload. Two equivalent paths: + +**A. `microbot-cli` wrapper** (preferred — handles auth automatically): + +```bash +../Microbot/microbot-cli scripts reload --name +../Microbot/microbot-cli scripts health +``` + +**B. Raw HTTP (127.0.0.1:8081)** when the CLI isn't enough: + +```bash +# Reload an existing deployment in place +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"name":""}' \ + http://127.0.0.1:8081/scripts/deploy/reload + +# Or deploy a fresh source file (compile + load + start via URLClassLoader + Guice) +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"source":"","className":""}' \ + http://127.0.0.1:8081/scripts/deploy + +# List current deployments +curl -sS "http://127.0.0.1:8081/scripts/deploy" + +# Undeploy (stop + unload) +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"name":""}' \ + http://127.0.0.1:8081/scripts/deploy/undeploy +``` + +Dynamic deploys are also the right tool for **runtime probe scripts** — minimal `@PluginDescriptor` classes you author on the fly to inspect engine state (varbits, widget trees, scene objects) without touching the target plugin. See `scripts/test_hot_reload.py` for a worked deploy/reload/undeploy lifecycle. Core implementation lives in `agentserver/scripting/DynamicScriptManager.java` and `DynamicScriptCompiler.java`. + +Ask Sonnet to return compile errors verbatim on failure. If the build fails, **you** fix the source — do not ask Sonnet to interpret compile errors. + +**Full client restart is only justified when** you changed: `@PluginDescriptor` field values (name, minClientVersion, iconUrl), `dependencies.txt`, `PluginConstants.java`, or Guice-bound singletons wired at client startup. Even then, try a reload first — if it fails cleanly with a classloader error, *then* restart: + +```bash +pkill -f 'net.runelite.client.RuneLite' || true +until ! curl -sS --max-time 1 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 1; done +./gradlew run --args='--debug' > /tmp/microbot-hub.log 2>&1 & +until curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 2; done +../Microbot/microbot-cli login now --timeout 60 +../Microbot/microbot-cli widgets click --text "Click here to play" # if present +``` + +### Stage 7 — Verify + +Have Sonnet restart the plugin via `/scripts/start` and re-collect the same state snapshots you captured in Stage 3. **You (Opus) decide** whether: + +1. The bug no longer reproduces (compare before/after state snapshots, log lines). +2. No new errors in `/tmp/microbot-hub.log`. +3. No other plugin behavior broke (glance at `/scripts/status` for anything else that was running). + +If verified → Stage 8. If not → increment patch-attempt counter, return to Stage 4. Stop on the 5th failure. + +### Stage 8 — Report *(Opus only)* + +Leave the plugin source edits **uncommitted**. Do not `git add` or `git commit` — the user will review the diff. + +Print a detailed explanation with these sections: + +- **Bug:** one-sentence restatement. +- **Plugin:** which plugin, which folder. +- **Reproduction:** how you triggered it, what state snapshot surfaced the symptom (quote the relevant JSON field or log line). +- **Root cause:** the specific code path and why it was broken. Cite `file:line`. +- **Fix:** what you changed and why, including the version bump. Cite `file:line` for each edit. +- **Verification:** what you ran, what the output was, what changed between before/after. +- **Attempts:** if you tried multiple patches, briefly list the ones that didn't work and why. + +## Reference docs (read on demand, don't duplicate) + +- `CLAUDE.md` — project rules (plugin descriptor fields, version bumping, threading, event subscription). +- `docs/PLUGIN_DEBUGGING_NOTES.md` — **read first** when symptoms look familiar; covers instanced-region coords, Queryable API auto-walk, static field leakage, agent-server `curl` recipes. +- `docs/AGENT_SERVER.md` — full HTTP endpoint reference for the agent server on :8081. +- `docs/MICROBOT_CLI.md` — CLI command reference. The CLI is at `../Microbot/microbot-cli` and wraps most endpoints with auth. +- `docs/SCRIPT_LIFECYCLE_API.md` — start/stop/status/results endpoints for automated plugin testing. +- `src/test/java/net/runelite/client/ScriptLifecycleTest.java` — worked example of the full login → start → poll → results → stop cycle in Java. +- `../Microbot/AGENTS.md` — non-negotiable engine rules (cache API, no client-thread blocking). Applies to plugin code that calls client APIs. + +## Pitfalls + +- **Client already running:** don't start another. Check `:8081/state` first. +- **Never restart the client to test a script change.** Hot-reload via `microbot-cli scripts reload` or `POST /scripts/deploy/reload`. Client restarts destroy the live game state you need for verification and cost ~90s per iteration. Restart is only justified for `@PluginDescriptor` field changes, `dependencies.txt` changes, `PluginConstants.java` changes, or Guice-bound singletons wired at startup — and even then, try reload first. +- **Stuck detection:** always run the stuck-detector subagent in the background once the plugin is running. Do not wait for arbitrary sleep windows to discover a hang — the watcher signals you in ~5s and hands you the evidence. +- **This skill does not patch engine code.** If root-cause sits in `../Microbot/` (client APIs, pathfinder, cache), stop and tell the user. They have a separate engine-debug workflow. +- **Token auth:** prefer `../Microbot/microbot-cli` where available — it reads `~/.microbot/agent-token` automatically. For endpoints the CLI doesn't wrap (`/scripts/deploy*`, `/state`, `/objects`), use curl with `-H "X-Agent-Token: $(cat ~/.microbot/agent-token)"`. +- **The `microbot-cli objects` command ignores `--id` and `--distance`.** For precise filters, curl `/objects` directly (see PLUGIN_DEBUGGING_NOTES.md §1). +- **Login welcome screen:** after `login now`, the "Click here to play" widget may still cover the game view. Dismiss before probing in-game state (per feedback memory). +- **Static field leakage:** plugins that keep `static` mutable state (collections, timers) will surprise you after stop/start. If the bug only reproduces on a second run, check for `static` fields not reset in `startUp()`. +- **`setTarget(null)` in PestControl:** that one-shot clear on instance exit is load-bearing (per project memory). Don't "clean it up" without understanding why it's there. +- **Version bump:** every patch requires a version bump in the plugin's `static final String version` field. The build system uses it for JAR naming and `plugins.json`. +- **Don't commit:** leave the diff for the user. diff --git a/.gitignore b/.gitignore index 8a3ced8fbb..fe0cb7232a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ nb-configuration.xml nbproject/ public/** /CreatePluginDocs.ps1 +.worktrees/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 7371f88eea..0000000000 --- a/AGENTS.md +++ /dev/null @@ -1,238 +0,0 @@ -# agents.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Overview - -Microbot Hub is a community plugin repository for the Microbot RuneLite client. It maintains a separation between core client functionality and community-contributed plugins, allowing rapid plugin development without affecting client stability. Each plugin is independently built, versioned, and packaged for GitHub Releases. - -## Build System Architecture - -The build system uses **Gradle with custom plugin discovery and packaging**: - -- **Dynamic Plugin Discovery**: `build.gradle` scans `src/main/java/net/runelite/client/plugins/microbot/` for directories containing `*Plugin.java` files -- **Per-Plugin Source Sets**: Each discovered plugin gets its own Gradle source set, compile task, and shadow JAR task -- **Gradle Helper Scripts**: Core build logic lives in: - - `gradle/project-config.gradle` - centralized configuration (JDK version, paths, GitHub release URLs, client version) - - `gradle/plugin-utils.gradle` - plugin discovery, descriptor parsing, JAR creation, SHA256 hashing - -### Build Commands - -```bash -# Build all plugins -./gradlew clean build - -# Build specific plugin(s) only (much faster for iteration) -./gradlew build -PpluginList=PestControlPlugin -./gradlew build -PpluginList=PestControlPlugin,AutoMiningPlugin - -# Run tests (tests have access to all plugin source sets) -./gradlew test - -# Generate plugins.json metadata file with SHA256 hashes (requires exact JDK 11) -./gradlew generatePluginsJson - -# Copy plugin documentation to public/docs/ -./gradlew copyPluginDocs - -# Launch RuneLite debug session with plugins from Microbot.java -./gradlew run --args='--debug' - -# Validate JDK version -./gradlew validateJdkVersion -``` - -## Plugin Structure - -Each plugin lives in its own package under `src/main/java/net/runelite/client/plugins/microbot//`: - -``` -/ -├── Plugin.java # Main plugin class with @PluginDescriptor -├── Script.java # Script logic extending Script class -├── Config.java # Configuration interface (optional) -├── Overlay.java # UI overlay (optional) -└── Additional support classes -``` - -Matching resources under `src/main/resources/net/runelite/client/plugins/microbot//`: - -``` -/ -├── dependencies.txt # Maven coordinates (optional) -└── docs/ - ├── README.md # Plugin documentation - └── assets/ # Screenshots, icons, etc. -``` - -## Plugin Descriptor Anatomy - -Every plugin **must** have a `@PluginDescriptor` annotation with these **required** fields: - -- `name` - Display name (use `PluginConstants.DEFAULT_PREFIX` or create custom prefix) -- `version` - Semantic version string (store in `static final String version` field) -- `minClientVersion` - Minimum Microbot client version required - -Important **optional** fields: - -- `authors` - Array of author names -- `description` - Brief description shown in plugin panel -- `tags` - Array of tags for categorization -- `iconUrl` - URL to icon image (shown in client hub) -- `cardUrl` - URL to card image (shown on website) -- `enabledByDefault` - Use `PluginConstants.DEFAULT_ENABLED` (currently `false`) -- `isExternal` - Use `PluginConstants.IS_EXTERNAL` (currently `true`) - -Example: -```java -@PluginDescriptor( - name = PluginConstants.MOCROSOFT + "Pest Control", - description = "Supports all boats, portals, and shields.", - tags = {"pest control", "minigames"}, - authors = { "Mocrosoft" }, - version = PestControlPlugin.version, - minClientVersion = "1.9.6", - iconUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/icon.png", - cardUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/card.png", - enabledByDefault = PluginConstants.DEFAULT_ENABLED, - isExternal = PluginConstants.IS_EXTERNAL -) -@Slf4j -public class PestControlPlugin extends Plugin { - static final String version = "2.2.7"; - // ... -} -``` - -## PluginConstants - -The `PluginConstants.java` file is **shared across all plugins** (included in each JAR during build). It contains: - -- Standardized plugin name prefixes (e.g., `DEFAULT_PREFIX`, `MOCROSOFT`, `BOLADO`) -- Global defaults: `DEFAULT_ENABLED = false`, `IS_EXTERNAL = true` - -When creating a new plugin prefix, add it to `PluginConstants.java` for consistency. - -## Adding External Dependencies - -If a plugin needs additional libraries beyond the Microbot client: - -1. Create `src/main/resources/net/runelite/client/plugins/microbot//dependencies.txt` -2. Add Maven coordinates, one per line: - ``` - com.google.guava:guava:33.2.0-jre - org.apache.commons:commons-lang3:3.14.0 - ``` -3. The build system automatically includes these in the plugin's shadow JAR - -## Testing and Debugging Plugins - -### Running Plugins in Debug Mode - -1. Edit `src/test/java/net/runelite/client/Microbot.java` -2. Add your plugin class to the `debugPlugins` array: - ```java - private static final Class[] debugPlugins = { - YourPlugin.class, - AutoLoginPlugin.class - }; - ``` -3. Run `./gradlew run --args='--debug'` or use your IDE's run configuration - -### Running Tests - -- Tests live in `src/test/java/` -- Test classes have access to all plugin source sets (configured in `build.gradle`) -- Use `./gradlew test` to run all tests - -## Version Management - -- **Always increment the plugin version** when making changes (even small fixes) -- Store version in a static field: `static final String version = "1.2.3";` -- Follow semantic versioning: `MAJOR.MINOR.PATCH` -- The version is used for JAR naming, GitHub release assets, and `plugins.json` generation - -## Git Workflow - -Based on recent commits: - -- Use conventional commit prefixes: `fix:`, `feat:`, `docs:`, etc. -- Include PR references when applicable: `fix: description (#123)` -- Work on feature branches, merge to `development`, create PRs to `main` -- Current branch: `development`, main branch: `main` - -## Publishing Workflow - -1. Build plugins: `./gradlew build` -2. Generate metadata: `./gradlew generatePluginsJson` (requires JDK 11 exactly) -3. Copy documentation: `./gradlew copyPluginDocs` -4. Upload `build/libs/-.jar` and updated `public/docs/plugins.json` as assets on the GitHub release tagged with `` (or `latest-release` for the stable tag): `https://github.com/chsami/Microbot-Hub/releases/download//-.jar` - -## Important Implementation Details - -- **Local Microbot Client Source**: The latest Microbot client source lives in the sibling `Microbot` folder (`../Microbot/`) on the `development` branch. This is the authoritative, up-to-date client codebase. When you need to look up client APIs, utility classes (e.g., `Rs2Bank`, `Rs2Inventory`, `Rs2Walker`), or understand how the client works, reference that repository directly. -- **Java Version**: JDK 11 (configured in `project-config.gradle` with `TARGET_JDK_VERSION = 11`, vendor `ADOPTIUM`) -- **Microbot Client Dependency**: Defaults to the latest version resolved via `https://microbot.cloud/api/version/client`, falling back to `2.0.61` if lookup fails. Artifacts come from GitHub Releases (`https://github.com/chsami/Microbot/releases/download//microbot-.jar`). Override with `-PmicrobotClientVersion=` or `-PmicrobotClientVersion=latest`, or supply a local JAR for offline work via `-PmicrobotClientPath=/absolute/path/to/microbot-.jar` -- **Plugin Release Tag**: `plugins.json` uses a stable release tag (`latest-release`) so download URLs stay constant: `https://github.com/chsami/Microbot-Hub/releases/download/latest-release/-.jar`. Override with `-PpluginsReleaseTag=` if needed. -- **Shadow JAR Excludes**: Common exclusions defined in `plugin-utils.gradle` include `docs/**`, `dependencies.txt`, metadata files, and module-info -- **Reproducible Builds**: JAR tasks disable file timestamps, use reproducible file order, and normalize file permissions to `0644` -- **Descriptor Parsing**: Build system uses regex to extract plugin metadata from Java source files (see `getPluginDescriptorInfo` in `plugin-utils.gradle`) - -## Plugin Discovery Logic - -When you run `./gradlew build`: - -1. Scans `src/main/java/net/runelite/client/plugins/microbot/` for directories -2. Finds directories containing a file matching `*Plugin.java` -3. Creates a plugin object with: `name` (class name without .java), `sourceSetName` (directory name), `dir`, `javaFile` -4. Filters by `-PpluginList` if provided -5. For each plugin: - - Creates dedicated source set - - Configures compilation classpath with Microbot client - - Creates shadow JAR task with plugin-specific dependencies - - Parses `@PluginDescriptor` for metadata - - Computes SHA256 hash of JAR for `plugins.json` - -## Microbot CLI & Agent Server - -The Microbot client embeds an HTTP server (Agent Server plugin, port 8081) that the Hub uses for automated testing. Reference docs are mirrored in this repo: - -- **CLI command reference**: `docs/MICROBOT_CLI.md` — login, script lifecycle, inventory, NPCs, walking, banking, etc. -- **HTTP API summary**: `docs/AGENT_SERVER.md` — all endpoints, login error detection, script result submission. -- **Script lifecycle API**: `docs/SCRIPT_LIFECYCLE_API.md` — start/stop/status/results endpoints and automated testing flow. -- **Test example**: `src/test/java/net/runelite/client/ScriptLifecycleTest.java` — demonstrates the full login → start → poll → results → stop cycle. - -Key capabilities for Hub plugin testing: -- **Login control**: `POST /login` blocks until login succeeds or fails, returning a definitive `success` boolean and `loginError` on failure (non-member on members world, bans, auth failures). Auto-dismisses error dialogs on retry — no manual intervention needed. -- **Script lifecycle**: Start/stop plugins by class name via HTTP, poll runtime status, submit and retrieve structured test results. -- **Java result API**: Hub scripts can call `ScriptResultStore.submit(className, data)` directly from within the JVM. - -## Common Patterns - -- Plugins extending `SchedulablePlugin` implement `getStartCondition()` and `getStopCondition()` for scheduler integration -- Use `@Inject` for dependency injection (configs, overlays, scripts) -- Config classes use `@Provides` methods to register with `ConfigManager` -- Overlays are registered in `startUp()`, unregistered in `shutDown()` -- Use `@Subscribe` for event handling (ChatMessage, GameTick, etc.) - -## Threading - -Scripts run on a scheduled executor thread, but certain RuneLite API calls (widgets, game objects, etc.) must run on the client thread: - -```java -// Use invoke() for client thread operations -TrialInfo info = Microbot.getClientThread().invoke(() -> TrialInfo.getCurrent(client)); - -// For void operations -Microbot.getClientThread().invoke(() -> { - // client thread code here -}); -``` - -**Always use `Microbot.getClientThread().invoke()`** when accessing: -- Widgets (`client.getWidget()`, `widget.isHidden()`) -- Game objects that aren't cached -- Player world view (`client.getLocalPlayer().getWorldView()`) -- `BoatLocation.fromLocal()` - accesses player world view internally -- `TrialInfo.getCurrent()` - accesses widgets internally -- Any RuneLite API that throws "must be called on client thread" diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000000..681311eb9c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 2eb1ddf950..55933f55b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -127,6 +127,8 @@ If a plugin needs additional libraries beyond the Microbot client: ## Testing and Debugging Plugins +**Before chasing a "script does nothing" bug, read [`docs/PLUGIN_DEBUGGING_NOTES.md`](docs/PLUGIN_DEBUGGING_NOTES.md).** It documents the recurring failure modes in Hub plugins (instanced-region coordinate mismatches, the new Queryable API not auto-walking, null-guard predicates masking broken lookups, static field leakage across plugin restarts, etc.) and the agent-server `curl` workflow for inspecting live state instead of theorizing from code. + ### Running Plugins in Debug Mode 1. Edit `src/test/java/net/runelite/client/Microbot.java` @@ -207,6 +209,35 @@ Key capabilities for Hub plugin testing: - **Script lifecycle**: Start/stop plugins by class name via HTTP, poll runtime status, submit and retrieve structured test results. - **Java result API**: Hub scripts can call `ScriptResultStore.submit(className, data)` directly from within the JVM. +## Dynamic Script Deployment (Hot-Reload) + +**Core Mechanism:** Compile Java source → load via custom URLClassLoader → inject into Guice → start as a RuneLite plugin. No client restart needed. + +**HTTP endpoints (127.0.0.1:8081):** +- `POST /scripts/deploy` — compile & start +- `POST /scripts/deploy/reload` — recompile in place +- `POST /scripts/deploy/undeploy` — stop & unload +- `GET /scripts/deploy` — list deployments + +**Core files:** `agentserver/scripting/DynamicScriptManager.java`, `DynamicScriptCompiler.java`, `handler/DynamicScriptDeployHandler.java` + +### Supporting Systems + +1. **microbot-cli** — bash wrapper for the agent server (`./microbot-cli scripts deploy|reload|undeploy|health|results`) +2. **Agent Server API** — query state (NPCs, objects, inventory, widgets) and drive the client (walk, interact, dialogue). Full reference in `docs/AGENT_SERVER.md`. +3. **Probe plugin pattern** — minimal `@PluginDescriptor` classes deployed to inspect engine state, iterate with hot-reload. Used by the `/debugger` skill. +4. **StateMachineScript** — base class for multi-phase scripts; transitions observable via `GET /debug/snapshot?script=Name`. +5. **ScriptResultStore** — scripts submit results in-process; retrieve via `/scripts/results` for test workflows. +6. **`scripts/test_hot_reload.py`** — working end-to-end example of the deploy/reload/undeploy lifecycle. + +### Typical Loop + +``` +edit source → microbot-cli scripts reload → microbot-cli scripts health → observe logs → repeat +``` + +Everything binds to `127.0.0.1` only. + ## Common Patterns - Plugins extending `SchedulablePlugin` implement `getStartCondition()` and `getStopCondition()` for scheduler integration diff --git a/build.gradle b/build.gradle index 3888d9554f..df23ae3d9d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,19 @@ apply from: 'gradle/plugin-utils.gradle' def microbotClientVersion = project.ext.getMicrobotClientVersion() def microbotClientPath = project.findProperty("microbotClientPath") +if (!microbotClientPath && project.hasProperty("microbotClientDir")) { + def clientDir = file(project.findProperty("microbotClientDir")) + if (clientDir.isDirectory()) { + def match = clientDir.listFiles()?.findAll { it.name ==~ /microbot-.*\.jar/ && !it.name.contains('sources') && !it.name.contains('shaded') } + ?.sort { -it.lastModified() } + ?.first() + if (match) { + microbotClientPath = match.absolutePath + logger.lifecycle("🤖 Resolved local microbot client JAR: ${microbotClientPath}") + } + } +} + // Java toolchain configuration java { toolchain { diff --git a/docs/PLUGIN_DEBUGGING_NOTES.md b/docs/PLUGIN_DEBUGGING_NOTES.md new file mode 100644 index 0000000000..00c061d225 --- /dev/null +++ b/docs/PLUGIN_DEBUGGING_NOTES.md @@ -0,0 +1,202 @@ +# Plugin Debugging Notes + +Lessons learned from debugging Hub plugins. Read this before chasing a "script silently does nothing" bug — it'll save you several rounds of guessing. + +## 1. Use the agent server. Stop guessing. + +The Microbot client embeds an HTTP agent server (port 8081, plugin name "Agent Server") that exposes the live scene cache, player state, inventory, NPCs, and dialogue. Two `curl` calls usually beat an hour of staring at code. + +```bash +# What does the player actually see right now? +curl -s 'http://127.0.0.1:8081/state' | jq + +# What's actually in the scene cache? +curl -s 'http://127.0.0.1:8081/objects?maxDistance=50&limit=10000' > /tmp/objs.json +python3 -c " +import json +data = json.load(open('/tmp/objs.json')) +print('total:', data['total']) +# filter by id range, name, position — whatever you need +for o in data['objects']: + if o['id'] == 27383: # the id you care about + print(o) +" +``` + +The CLI wrapper at `../Microbot/microbot-cli` handles login, inventory, NPCs, dialogue, walking, banking, and script lifecycle. See `docs/MICROBOT_CLI.md` for the command reference. + +**The rule:** if a "script does nothing" bug has gone past two rounds of theorizing, stop and inspect the live state. Don't reason about what *should* be in the cache; ask the cache. + +### Pitfall: the CLI is missing some flags +The `microbot-cli objects` command currently ignores `--id` and `--distance` flags and just dumps the raw `/objects` endpoint with defaults. Use `curl` directly when you need precise filtering. The server-side parameters are documented in `docs/AGENT_SERVER.md`; for `/objects` they're `name`, `maxDistance` (default 20), `limit`. + +## 2. Instanced regions are everywhere — and they break worldpoint math + +Several minigames and quest areas (tithe farm, raids, gauntlet, soul wars, fight caves, house, theatre, etc.) load tiles from a *template region* into the player's scene. Inside an instance: + +- The actual scene tiles live at template coordinates, often in the high X/Y corner of the world (e.g. tithe farm patches at `(13602, 7000)`). +- The player's "logical" overworld coordinate is somewhere completely different (e.g. tithe farm logical pos `(1806, 3501)` in Hosidius). +- Both views of the player are reachable depending on which API you call. **They are not the same number, and they are not interchangeable.** + +### The two `getWorldLocation()` calls behave differently + +```java +// Returns the OVERWORLD coord (the "logical" position, mirrored from the instance). +client.getLocalPlayer().getWorldLocation() + +// Returns the INSTANCE coord (where the tiles actually live in the loaded scene). +Rs2Player.getWorldLocation() +``` + +The Microbot wrapper checks `getTopLevelWorldView().getScene().isInstance()` and translates via `WorldPoint.fromLocalInstance(...)`. The raw client call doesn't. + +**TileObject coordinates always match the instance side**, because that's where the tile actually exists in the scene. So if you compute a target world point using `client.getLocalPlayer()...getRegionID()` (overworld region) and then try to match it against a tile object's `getWorldLocation()` (instance region), the lookup silently returns null. Forever. + +### Three things that work in instanced regions + +1. **`Rs2Player.getWorldLocation()`** — already handles the mirror. +2. **Local-region coordinates (`wp.getRegionX()` / `wp.getRegionY()`)** — these are `x & 63` / `y & 63`, intrinsic to the tile, *the same* for the template region and the instance because they're modulo-64 within a region. Match by these instead of by full world point if you need lookups that work across both contexts. +3. **The cache itself.** Query the cache by ID, name, or distance — don't reconstruct world points from scratch. + +### How to detect that you're inside an instance + +```java +boolean instanced = Microbot.getClient().getTopLevelWorldView().getScene().isInstance(); +``` + +Or just observe: if `Rs2Player.getWorldLocation()` is in the high-coord corner of the map (X > 6000 or so), you're in an instance. + +## 3. The new Queryable API does NOT auto-walk + +Legacy `Rs2GameObject.clickObject(TileObject, action)` (and every `interact(...)` overload that goes through it) automatically walks if the target is more than 51 tiles away: + +```java +// inside Rs2GameObject.clickObject — line ~1728 +if (Rs2Player.getWorldLocation().distanceTo(object.getWorldLocation()) > 51) { + Microbot.log("...too far, walking to the object...."); + Rs2Walker.walkTo(object.getWorldLocation()); + return false; +} +``` + +The new `Rs2TileObjectModel.click(action)` has **no equivalent**. It just dispatches a menu invoke at the current location. If you migrate `Rs2GameObject.interact(id, action)` to `cache.query().withId(id).nearest().click(action)` and the object is out of click range, your script will silently fail every tick forever. + +**Mitigation:** wrap migrated interactions in a helper that walks first. + +```java +private static boolean interactWithObject(int id, String action) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query() + .withId(id) + .nearest(); + if (model == null) { + Microbot.log("Object id " + id + " not in scene"); + return false; + } + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.distanceTo(model.getWorldLocation()) > 51) { + Microbot.log("Object id " + id + " too far, walking..."); + Rs2Walker.walkTo(model.getWorldLocation()); + return false; + } + return action == null ? model.click() : model.click(action); +} +``` + +Or follow the AIOFighter pattern (BankerScript:482): bound the query with `.nearest(20)` and rely on a *separate* walker call upstream to get into range first. Either is fine — just don't assume `click()` walks. + +## 4. Null-safe predicates can mask the underlying bug + +Migrating from raw `TileObject` (which NPEs on `null.getId()`) to `Rs2TileObjectModel` with null-guarded predicates feels safer: + +```java +// Before (NPEs if model is null) +public boolean isEmptyPatch() { + return gameObject.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; +} + +// After (graceful) +public boolean isEmptyPatch() { + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; +} +``` + +This is correct, but be aware: if the lookup is broken and `getGameObject()` returns null *for every plant*, all your predicates return `false`. Code that branches on those predicates will silently take the "no plants need anything done" path. Symptom: "script does nothing." Cause: "lookup broken since the migration." + +Watch out for predicates that flip in the *wrong direction* on null. A `noneMatch(isEmptyPatch)` returns `true` when nothing is recognized as empty — which can also mean "we have no data yet." If that gates a once-only state transition (like `allPlanted = true`), you can permanently stick the script in the wrong branch on the very first tick. Gate state-flipping predicates on `allMatch(p -> p.getGameObject() != null)` first so you only act on real data. + +## 5. Static fields leak across plugin restarts + +A common Microbot plugin pattern: + +```java +public class FooPlugin extends Plugin { + private final FooScript fooScript = new FooScript(); // <-- final +} + +public class FooScript extends Script { + public static boolean init = true; // <-- static + public static List things = ...; // <-- static + public static FooState state = ...; // <-- static + private boolean allDone = false; // <-- instance, but lives as long as the plugin +} +``` + +When the user disables and re-enables the plugin, neither the `static` fields nor the instance fields get reset — the script object is `final` on the plugin, and the static fields persist across the entire JVM session. So a fresh plugin start can inherit `init = false`, a stale `things` list from a previous session, or `allDone = true` from a finished run. + +**Mitigation:** reset everything at the top of `run()`, *before* you schedule the executor lambda: + +```java +public boolean run(FooConfig config) { + init = true; + things = new ArrayList<>(); + state = STARTING; + allDone = false; + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(...); +} +``` + +This is cheap insurance and makes the plugin behave the same on the first start as on the tenth restart. + +## 6. Don't use `Microbot.showMessage` from script threads + +`Microbot.showMessage` opens a Swing modal via `SwingUtilities.invokeAndWait`. If your script's executor is ticking every 100 ms, the next tick will interrupt the AWT-blocking thread and you'll get a flood of `InterruptedException` traces with no actual message ever shown to the user. + +For debug/log indicators, use `Microbot.log` instead — it's just slf4j, never blocks the AWT thread, and shows up in the same place users already look. + +Reserve `showMessage` for hard-stop conditions where the script is about to `shutdown()` and the user genuinely needs to see the message (and even then, only call it once). + +## 7. Don't trust hardcoded coordinates without verifying against the cache + +Hardcoded coordinates in plugins drift over time: +- Jagex moves objects in updates. +- An area gets converted to instanced and the local scene anchor changes. +- The original developer used a different convention (corner vs center, 0-indexed vs 1-indexed). + +If a plugin's hardcoded `(regionX, regionY)` lookups stop matching, the symptom is always "script does nothing." Confirm the actual coordinates with one CLI call before patching: + +```bash +curl -s 'http://127.0.0.1:8081/objects?maxDistance=100&limit=10000' \ + | python3 -c " +import json, sys +data = json.load(sys.stdin) +# filter for whatever id you're looking for +for o in data['objects']: + if o['id'] == 27383: + x, y = o['position']['x'], o['position']['y'] + print(f'world=({x},{y}) regionLocal=({x & 63},{y & 63})') +" +``` + +For tithe farm specifically, the lane definitions in `TitheFarmingScript.init(...)` are off by 1 in both x and y from the actual instance patches. Rather than rewrite all four lane lists, the plant lookup uses a `dx <= 1 && dy <= 1` tolerance plus a tithe-patch-ID filter — patches are 3 tiles apart so the tolerance can never grab a wrong neighbour. This pattern (tolerance + ID filter) is a generic safety net for any future hardcoded-coordinate drift. + +## Quick triage checklist + +When a Hub plugin "doesn't do anything": + +1. **Is the script even running?** Add a `Microbot.log("X script started")` at the top of `run()` and watch the log on the next plugin start. +2. **What state is it in?** Most plugins have an overlay showing the current state — read it. If not, log the state on each tick (or on transition). +3. **Is the cache empty, or just the wrong thing?** `curl 'http://localhost:8081/objects?maxDistance=20&limit=200'` and look at what's actually there. +4. **Is the player where you think?** `curl 'http://localhost:8081/state'` — and if you're inside an instance, expect logical vs instance coordinates to differ. +5. **Are you hitting a null-guard short-circuit?** Search the script for predicates that return `false` on null and gate behavior. If the lookup is broken, every predicate returns `false` and the script takes the "do nothing" branch. +6. **Did the static state leak from a previous run?** Check whether `run()` resets the static fields the script relies on. diff --git a/gradle/plugin-utils.gradle b/gradle/plugin-utils.gradle index 7e17bcecb0..de272f7f2c 100644 --- a/gradle/plugin-utils.gradle +++ b/gradle/plugin-utils.gradle @@ -36,6 +36,7 @@ ext { java { srcDirs = ["src/main/java"] include "${project.ext.getPluginsIncludePath()}/PluginConstants.java" + include "${project.ext.getPluginsIncludePath()}/Rs2Leprechaun.java" include "${project.ext.getPluginsIncludePath()}/${plugin.dir.name}/**" } resources { diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java index dcfc7691c6..04e1a3866e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java @@ -28,7 +28,7 @@ @Slf4j public class AmmoniteCrabPlugin extends Plugin { - public final static String version = "1.1.2"; + public final static String version = "1.1.3"; @Inject public AmmoniteCrabScript ammoniteCrabScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java index 3a78e58abc..543c2f8b8f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.AmmoniteCrabs; import net.runelite.api.GameState; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,8 +12,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.security.Login; @@ -218,12 +216,13 @@ private void walkBack(AmmoniteCrabConfig config) { } } - // Attack all scattered crabs in the area private void attackScatteredCrabs(AmmoniteCrabConfig config) { - var ammoniteCrabs = Rs2Npc.getNpcs("Ammonite Crab", true).filter(x -> x != null && !x.isDead() && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) > 1 && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) < 15).collect(Collectors.toList()); + var ammoniteCrabs = Microbot.getRs2NpcCache().query().withName("Ammonite Crab") + .where(x -> x.getNpc() != null && !x.getNpc().isDead() && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) > 1 && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) < 15) + .toList(); for (Rs2NpcModel ammoniteCrab : ammoniteCrabs) { - if (ammoniteCrab != null && !ammoniteCrab.isDead()) { - Rs2Npc.attack(ammoniteCrab); + if (ammoniteCrab != null && !ammoniteCrab.getNpc().isDead()) { + ammoniteCrab.click("Attack"); Rs2Player.waitForAnimation(1600); sleep(1600, 2400); } @@ -237,18 +236,17 @@ private void attackScatteredCrabs(AmmoniteCrabConfig config) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Rs2Npc.getNpcs("Fossil Rock", true).collect(Collectors.toList()); + List npcs = Microbot.getRs2NpcCache().query().withName("Fossil Rock").toListOnClientThread(); if (npcs.isEmpty()) { return true; } - for (NPC ammoniteRock : npcs) { - //ignore ammonitecrabs far away from the player - if (!ammoniteRock.getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea())) + for (Rs2NpcModel ammoniteRock : npcs) { + if (!ammoniteRock.getNpc().getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea())) continue; - return false; //found a fossil rock crab near the player + return false; } - return true; //did not find any fossil rocks near the player + return true; } private void resetAggro(AmmoniteCrabConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java index 2b8d71b578..faa4fddeb1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java @@ -32,7 +32,7 @@ ) public class CalcifiedRockMinerPlugin extends Plugin { - public final static String version = "1.1.1"; + public final static String version = "1.1.4"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java index 1e4eb79355..4e6144bb38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.CalcifiedRockMiner; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; @@ -8,6 +7,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; @@ -15,7 +15,6 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -83,8 +82,7 @@ public boolean run(net.runelite.client.plugins.microbot.CalcifiedRockMiner.Calci private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig config) { if (Rs2Inventory.isFull()) { if (config.dropDeposits()) { - Rs2Inventory.dropAll("Calcified deposit"); - Rs2Inventory.dropAll("uncut"); + Rs2Inventory.dropAll(false, "calcified deposit", "uncut"); return; } else if (config.crushDeposits()) { BOT_STATUS = CalcifiedRockMinerState.CRUSHING; @@ -107,20 +105,30 @@ private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMine } if (config.focusCrackedWaterDeposits() && !Rs2Player.isMoving()) { - var weepingRocks = Rs2GameObject.getDecorativeObjects(x -> x.getId() == WEEPING_ROCK, Rs2Player.getWorldLocation()); - if (weepingRocks != null && !weepingRocks.isEmpty()) { - var rock = weepingRocks.stream().findFirst().get(); - MoveCameraToRock(rock.getWorldLocation()); - var distance = rock.getLocalLocation().distanceTo(Rs2Player.getLocalLocation()); - // 128 == 1 tile, so we must be mining the tear, if above, we should move to the tear - if (distance > 128 || !Rs2Player.isAnimating()) { - Rs2Camera.turnTo(rock.getLocalLocation(), 45); - Microbot.getMouse().click(rock.getCanvasLocation()); - Rs2Player.waitForXpDrop(Skill.MINING, true); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - sleepUntil(Rs2Player::isAnimating, 5000); - return; + Rs2TileObjectModel weepingRock = Microbot.getRs2TileObjectCache().query() + .withId(WEEPING_ROCK) + .within(Rs2Player.getWorldLocation(), 15) + .first(); + if (weepingRock != null) { + Rs2TileObjectModel tearRock = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(weepingRock.getWorldLocation(), 2) + .withName("Calcified rocks") + .first()); + if (tearRock != null) { + MoveCameraToRock(tearRock.getWorldLocation()); + var distance = tearRock.getLocalLocation().distanceTo(Rs2Player.getLocalLocation()); + // 128 == 1 tile, so we must be mining the tear, if above, we should move to the tear + if (distance > 128 || !Rs2Player.isAnimating()) { + Rs2Camera.turnTo(tearRock.getLocalLocation(), 45); + if (tearRock.click()) { + Rs2Player.waitForXpDrop(Skill.MINING, true); + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + sleepUntil(Rs2Player::isAnimating, 5000); + } + return; + } } } } @@ -129,10 +137,14 @@ private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMine return; } - GameObject rock = Rs2GameObject.findReachableObject("Calcified rocks", true, 12, CALCIFIED_ROCK_LOCATION); + Rs2TileObjectModel rock = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(CALCIFIED_ROCK_LOCATION, 12) + .withName("Calcified rocks") + .nearestReachable()); if (rock != null && shouldTryMiningAgain) { MoveCameraToRock(rock.getWorldLocation()); - if (Rs2GameObject.interact(rock)) { + if (rock.click()) { Rs2Player.waitForXpDrop(Skill.MINING, true); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -170,11 +182,23 @@ private boolean hopIfTooManyPlayersNearby(net.runelite.client.plugins.microbot.C return false; } + private boolean hasHammer() { + return Rs2Inventory.hasItem("hammer") || Rs2Equipment.isWearing("hammer"); + } + private void handleCrushing(net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig config) { - if (config.crushDeposits() && Rs2Inventory.hasItem("hammer") && Rs2Inventory.hasItem(29088)) { + if (config.crushDeposits() && hasHammer() && Rs2Inventory.hasItem(29088)) { if (Rs2Player.getWorldLocation().distanceTo(ANVIL) < 1) { Rs2Inventory.interact(29088, "use"); - Rs2GameObject.interact("Anvil"); + Rs2TileObjectModel anvil = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(ANVIL, 3) + .withName("Anvil") + .nearestReachable()); + if (anvil == null) { + return; + } + anvil.click(); sleep(400,600); Rs2Widget.sleepUntilHasWidget("How many would you like to smash?"); sleep(200,400); @@ -208,8 +232,7 @@ private void handleBanking(CalcifiedRockMinerConfig config) { Rs2Bank.walkToBank(BankLocation.CAM_TORUM); Rs2Bank.openBank(); sleepUntil(() -> Rs2Bank.isOpen(), 5000); - Rs2Bank.depositAll("Calcified deposit"); - Rs2Bank.depositAll("Uncut"); + Rs2Bank.depositAllExcept("pickaxe", "hammer"); Rs2Bank.closeBank(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java index cac0ad0044..61808ccfd1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java @@ -16,6 +16,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.List; @@ -327,20 +328,19 @@ private boolean isNearArea(DeadFallTrapHunting deadFallTrapHunting) { } private boolean handleExistingTraps(DeadFallTrapHunterPlugin plugin, DeadFallTrapHunterConfig config) { - // Filter for FULL traps and sort by time (traps about to collapse first) and then pick the first one var trapToHandle = plugin.getTraps().entrySet().stream() .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) .sorted((a, b) -> Double.compare(b.getValue().getTrapTimeRelative(), a.getValue().getTrapTimeRelative())).collect(Collectors.toList()).stream().findFirst().orElse(null); if (trapToHandle == null) return false; WorldPoint location = trapToHandle.getKey(); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving()) { - var gameObject = Rs2GameObject.getGameObject(location); + Rs2TileObjectModel gameObject = Microbot.getRs2TileObjectCache().query().within(location, 0).first(); if (gameObject != null) { if (Rs2Inventory.count() > 24) { forceDrop = true; Rs2Inventory.waitForInventoryChanges(8000); } - Rs2GameObject.interact(gameObject, "Check"); + gameObject.click("Check"); creaturesCaught++; sleep(config.minSleepAfterCatch(), config.maxSleepAfterCatch()); return true; @@ -350,8 +350,8 @@ private boolean handleExistingTraps(DeadFallTrapHunterPlugin plugin, DeadFallTra } private void setNewTrap(DeadFallTrapHunting deadFallTrapHunting, DeadFallTrapHunterConfig config) { - if (Rs2GameObject.exists(deadFallTrapHunting.getTrapId())) { - Rs2GameObject.interact(deadFallTrapHunting.getTrapId(), "Set-trap"); + if (Microbot.getRs2TileObjectCache().query().withId(deadFallTrapHunting.getTrapId()).count() > 0) { + Microbot.getRs2TileObjectCache().query().interact(deadFallTrapHunting.getTrapId(), "Set-trap"); sleep(config.minSleepAfterLay(), config.maxSleepAfterLay()); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java index 112286da94..df7b33c05c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java @@ -35,7 +35,7 @@ @Slf4j public class DemonicGorillaPlugin extends Plugin { - public final static String version = "1.2.4"; + public final static String version = "1.2.5"; private static final int DEMONIC_GORILLA_ROCK = 856; public static FixedSizeQueue lastLocation = new FixedSizeQueue<>(2); diff --git a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java index 55b111c0b7..d3869d7014 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java @@ -14,12 +14,10 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; -import net.runelite.client.plugins.microbot.util.reflection.Rs2Reflection; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -29,7 +27,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity.EXTREME; @@ -218,7 +215,7 @@ private void handleBanking(DemonicGorillaConfig config) { } private void handleFighting(DemonicGorillaConfig config) { - if (currentTarget == null || currentTarget.isDead()) { + if (currentTarget == null || currentTarget.getNpc().isDead()) { npcAnimationCount = 0; logOnceToChat("Target is null or dead"); handleNewTarget(config); @@ -246,7 +243,7 @@ private void handleTargetSelection() { // Ensure currently selected target is also who we are attacking if (currentTarget != null) { var tempTarget = getTarget(true); - if ((tempTarget != null && tempTarget.getIndex() != currentTarget.getIndex()) || currentTarget.isDead()) { + if ((tempTarget != null && tempTarget.getIndex() != currentTarget.getIndex()) || currentTarget.getNpc().isDead()) { logOnceToChat("Invalid target was selected, switching to correct enemy"); currentTarget = tempTarget; } @@ -258,14 +255,13 @@ private void handleTargetSelection() { logOnceToChat("Out of combat for 6 seconds, forcing new target"); currentTarget = getTarget(true); if (currentTarget != null) { - Rs2Npc.attack(currentTarget); + currentTarget.click("Attack"); } else { logOnceToChat("Unable to force new target, walking to gorillas and trying again"); Rs2Walker.walkTo(GORILLA_LOCATION); currentTarget = getTarget(true); - // Last attempt, just attack a gorilla if (currentTarget == null) { - Rs2Npc.attack("Demonic gorilla"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Demonic gorilla").interact("Attack")); } } outOfCombatTime = null; // Reset after forcing new target @@ -294,7 +290,7 @@ private void handleNewTarget(DemonicGorillaConfig config) { Rs2Player.eatAt(80); Rs2Player.drinkPrayerPotionAt(config.minEatPercent()); lootAttempted = true; - if (currentTarget != null && currentTarget.isDead()) { + if (currentTarget != null && currentTarget.getNpc().isDead()) { killCount++; currentTripKillCount++; } @@ -323,16 +319,16 @@ private void handleNewTarget(DemonicGorillaConfig config) { } private void attackGorilla(DemonicGorillaConfig config) { - if (currentTarget != null && !currentTarget.isDead()) { + if (currentTarget != null && !currentTarget.getNpc().isDead()) { Rs2Player.eatAt(config.minEatPercent()); Rs2Player.drinkPrayerPotionAt(config.minPrayerPercent()); if (currentTarget != null) { if (!Rs2Player.isAnimating(1600)) { - if (currentTarget != null && !currentTarget.isDead()) { + if (currentTarget != null && !currentTarget.getNpc().isDead()) { if (config.enableAutoSpecialAttacks()) { Rs2Combat.setSpecState(true, 500); } - var didWeAttack = Rs2Npc.attack(currentTarget); + var didWeAttack = currentTarget.click("Attack"); if (didWeAttack) { failedAttacks = 0; } else { @@ -354,8 +350,8 @@ private void handleDemonicGorillaAttacks(DemonicGorillaConfig config) { Rs2PrayerEnum newDefensivePrayer = null; boolean dodgedRock = false; - if (currentTarget != null && !currentTarget.isDead()) { - int currentAnimation = currentTarget.getAnimation(); + if (currentTarget != null && !currentTarget.getNpc().isDead()) { + int currentAnimation = currentTarget.getNpc().getAnimation(); var location = currentTarget.getWorldLocation(); // Handle prayer switching if (currentAnimation == DEMONIC_GORILLA_MAGIC_ATTACK) { @@ -405,7 +401,7 @@ private void handleDemonicGorillaAttacks(DemonicGorillaConfig config) { } if (!dodgedRock) { if ((currentGear == ArmorEquiped.RANGED || currentGear == ArmorEquiped.MAGIC) && (currentAnimation != DEMONIC_GORILLA_MELEE_ATTACK && currentAnimation != -1 && currentDefensivePrayer != Rs2PrayerEnum.PROTECT_MELEE)) { - var isMeleeDist = currentTarget.getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea()); + var isMeleeDist = currentTarget.getNpc().getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea()); if (isMeleeDist) { moveAwayFromTarget(); } @@ -502,36 +498,38 @@ public Rs2NpcModel getTarget() { } public Rs2NpcModel getTarget(boolean force) { - if (currentTarget != null && !currentTarget.isDead() && !force) { + if (currentTarget != null && !currentTarget.getNpc().isDead() && !force) { return currentTarget; } var interacting = Rs2Player.getInteracting(); if (interacting != null) { if (Objects.equals(interacting.getName(), "Demonic gorilla")) { - return (Rs2NpcModel) interacting; + var match = Microbot.getRs2NpcCache().query().withName("Demonic gorilla") + .where(n -> n.isInteractingWithPlayer()).nearest(); + if (match != null) return match; } } var playerLocation = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - var alreadyInteractingNpcs = Rs2Npc.getNpcsForPlayer("Demonic gorilla"); + var alreadyInteractingNpcs = Microbot.getRs2NpcCache().query().withName("Demonic gorilla") + .where(n -> n.isInteractingWithPlayer()).toList(); if (!alreadyInteractingNpcs.isEmpty()) { return alreadyInteractingNpcs.stream() .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))).get(); } - var demonicGorillaStream = Rs2Npc.getNpcs("Demonic gorilla"); - if (demonicGorillaStream == null) { + List demonicGorillas = Microbot.getRs2NpcCache().query().withName("Demonic gorilla").toListOnClientThread(); + if (demonicGorillas.isEmpty()) { logOnceToChat("No demonic gorilla found."); return null; } var player = Rs2Player.getLocalPlayer(); String playerName = player.getName(); - List demonicGorillas = demonicGorillaStream.collect(Collectors.toList()); for (Rs2NpcModel demonicGorilla : demonicGorillas) { if (demonicGorilla != null) { - var interactingTwo = demonicGorilla.getInteracting(); + var interactingTwo = demonicGorilla.getNpc().getInteracting(); String interactingName = interactingTwo != null ? interactingTwo.getName() : "None"; if (interactingTwo != null && Objects.equals(interactingName, playerName)) { return demonicGorilla; @@ -541,8 +539,8 @@ public Rs2NpcModel getTarget(boolean force) { logOnceToChat("Finding closest demonic gorilla."); return demonicGorillas.stream() - .filter(npc -> npc != null && !npc.isDead() && !npc.isInteracting()) - .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))).stream().findFirst() + .filter(npc -> npc != null && !npc.getNpc().isDead() && npc.getNpc().getInteracting() == null) + .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))) .orElse(null); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java index d6025404d8..f3e03a1efa 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java @@ -34,7 +34,7 @@ ) public class EnsouledHeadSlayerPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java index 89ed92b1a4..86a77a86db 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java @@ -13,13 +13,11 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.item.Rs2EnsouledHead; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Spell; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -114,7 +112,7 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { */ if (Microbot.getVarbitValue(Varbits.SPELLBOOK) != 3) { Microbot.log("On wrong spellbook, switching to Arceuus..."); - Rs2Npc.interact("Tyss", "Spellbook"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Tyss").interact("Spellbook")); } Rs2Combat.enableAutoRetialiate(); var ensouledHead = Rs2Inventory.count("ensouled"); @@ -138,9 +136,12 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { return; } - var enemy = Rs2Npc.getNpcsForPlayer("Reanimated", false).stream().filter(x -> !x.isDead() && - x.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 2).collect(Collectors.toList()); - if (enemy.isEmpty() && Rs2Player.getInteracting() == null && Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) == null) { + var enemy = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().contains("Reanimated") && n.isInteractingWithPlayer()) + .toList().stream() + .filter(x -> !x.getNpc().isDead() && x.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 2) + .collect(Collectors.toList()); + if (enemy.isEmpty() && Rs2Player.getInteracting() == null && Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() == null) { var ensouledHeadItem = Rs2Inventory.get("ensouled"); var spell = Arrays.stream(Rs2EnsouledHead.values()) .filter(x -> x.hasRequirements() && Objects.equals(x.getName(), ensouledHeadItem.getName())) @@ -150,17 +151,14 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { Rs2Magic.cast(spell.getMagicSpell()); sleepUntil(() -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY, 1000); Rs2Inventory.interact(ensouledHeadItem); - // NPC animation getting cast - sleepUntil(() -> Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) != null, 4000); - // NPC animation finished - NPC should be spawned - sleepUntil(() -> Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) == null, 4000); - // Without this delay, we won't wait for the NPC to spawn before trying other actions + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() != null, 4000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() == null, 4000); sleep(3500, 4500); } } else { var animmatedEnemy = enemy.stream().findFirst().orElse(null); - if (animmatedEnemy != null && !animmatedEnemy.isDead()) { - Rs2Npc.attack(animmatedEnemy); + if (animmatedEnemy != null && !animmatedEnemy.getNpc().isDead()) { + animmatedEnemy.click("Attack"); } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java index 6616d4704e..c9394e0410 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java @@ -1,19 +1,18 @@ package net.runelite.client.plugins.microbot.GemCrabKiller; -import net.runelite.api.GameObject; import net.runelite.api.ItemID; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -124,7 +123,7 @@ private void handleWaiting(GemCrabKillerConfig config) { if (waitingTimeStart == null) { waitingTimeStart = Instant.now(); } - if (Rs2Npc.getNpc(CRAB_NPC_ID) != null) { + if (Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; waitingTimeStart = null; return; @@ -171,12 +170,12 @@ private void handleBanking(GemCrabKillerConfig config) { } private void handleFighting(GemCrabKillerConfig config) { - var npc = Rs2Npc.getNpc(CRAB_NPC_ID); - var deadNpc = Rs2Npc.getNpc(CRAB_NPC_DEAD_ID); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest(); + Rs2NpcModel deadNpc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_DEAD_ID).nearest(); if (deadNpc != null) { totalCrabKills++; if (config.lootCrab() && Rs2Inventory.hasItem(" pickaxe", false) && !hasLooted) { - Rs2Npc.interact(deadNpc, "Mine"); + deadNpc.click("Mine"); Rs2Inventory.waitForInventoryChanges(2400); sleep(3000, 5000); hasLooted = true; @@ -185,7 +184,7 @@ private void handleFighting(GemCrabKillerConfig config) { return; } } - Rs2GameObject.interact(CAVE_ENTRANCE_ID, "Crawl-through"); + Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).interact("Crawl-through"); gemCrabKillerState = GemCrabKillerState.WAITING; return; } else { @@ -196,7 +195,7 @@ private void handleFighting(GemCrabKillerConfig config) { return; } if (!Rs2Player.isInCombat()) { - Rs2Npc.attack(npc); + npc.click("Attack"); } else { waitingTimeStart = null; } @@ -208,23 +207,19 @@ private void handleWalking() { } - var npc = Rs2Npc.getNpc(CRAB_NPC_ID); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest(); if (npc != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; return; } - // Check if we're near the cave entrance before walking - WorldPoint playerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - GameObject caveEntrance = Rs2GameObject.getGameObject(CAVE_ENTRANCE_ID, playerLoc); + Rs2TileObjectModel caveEntrance = Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).nearest(); if (caveEntrance != null) { - // Check if the cave entrance has the "Crawl-through" action - var composition = Microbot.getClientThread().runOnClientThreadOptional(() -> - Microbot.getClient().getObjectDefinition(CAVE_ENTRANCE_ID)).orElse(null); - if (composition != null && Rs2GameObject.hasAction(composition, "Crawl-through")) { - Rs2GameObject.interact(CAVE_ENTRANCE_ID, "Crawl-through"); - sleepUntil(() -> Rs2Npc.getNpc(CRAB_NPC_ID) != null, 5000); - if (Rs2Npc.getNpc(CRAB_NPC_ID) != null) { + var composition = caveEntrance.getObjectComposition(); + if (composition != null && java.util.Arrays.stream(composition.getActions()).anyMatch("Crawl-through"::equals)) { + Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).interact("Crawl-through"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null, 5000); + if (Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; } return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java index e5f976c056..5d4bc2c5da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java @@ -24,8 +24,8 @@ enum modeType { @ConfigItem( keyName = "override", - name = "Overide Start State?", - description = "Should we use digsite pendant from inventory/bank?", + name = "Override Start State?", + description = "Skip to a specific state instead of starting from Banking", position = 0 ) default boolean override() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java index f498e91c01..5232cfcdb3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class GiantSeaweedFarmerPlugin extends Plugin { - public final static String version = "1.2.0"; + public final static String version = "1.2.1"; private Instant scriptStartTime; @Inject private GiantSeaweedFarmerConfig config; @@ -46,7 +46,6 @@ protected String getTimeRunning() { @Override protected void startUp() throws AWTException { scriptStartTime = Instant.now(); - giantSeaweedFarmerScript.GSF_Running = true; if (overlayManager != null) { overlayManager.add(giantSeaweedFarmerOverlay); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java index ac5cb67792..7a34115fe2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java @@ -1,21 +1,20 @@ package net.runelite.client.plugins.microbot.GiantSeaweedFarmer; +import net.runelite.api.ObjectComposition; import net.runelite.api.Skill; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -38,11 +37,8 @@ public class GiantSeaweedFarmerScript extends Script { public static GiantSeaweedFarmerStatus BOT_STATE = GiantSeaweedFarmerStatus.BANKING; public boolean GSF_Running = true; private boolean BankSuccess = false; - private TileObject currentPatch; private List handledPatches = new ArrayList<>(); private final List patches = List.of(30500, 30501); - private static int lastVarbitValue = -1; - private long lastApparatusCheck = System.currentTimeMillis(); private static final WorldPoint FossilIslandDiveChest = new WorldPoint(3766, 3899, 0); private static final WorldPoint FarmGuildSpiritTree = new WorldPoint(1250, 3749, 0); @@ -59,6 +55,13 @@ public GiantSeaweedFarmerScript(GiantSeaweedFarmerPlugin giantSeaweedPlugin) { public boolean run(GiantSeaweedFarmerConfig config) { Microbot.enableAutoRunOn = false; this.config = config; + + BOT_STATE = GiantSeaweedFarmerStatus.BANKING; + BankSuccess = false; + GSF_Running = true; + handledPatches.clear(); + inCriticalSection = false; + if (config.useAntiBan()){GSF_AntiBan_Setup();} if (config.override()) { @@ -147,7 +150,7 @@ private void getToFossilIsland() { sleep(2000, 3000); Rs2Walker.walkTo(FarmGuildSpiritTree); sleep(550, 750); - Rs2GameObject.interact(FARMGUILD_SPIRITTREE, "Travel"); + Microbot.getRs2TileObjectCache().query().interact(FARMGUILD_SPIRITTREE, "Travel"); sleep(550, 750); Rs2Keyboard.keyPress(KeyEvent.VK_C); sleep(550, 750); @@ -161,47 +164,23 @@ private void getToFossilIsland() { } - // Track last varbit value to reduce log spam - - - // Using official RuneLite varbit ranges for seaweed patches - private static String getSeaweedPatchState(TileObject rs2TileObject) { - var game_obj = Rs2GameObject.convertToObjectComposition(rs2TileObject, true); - var varbitValue = Microbot.getVarbitValue(game_obj.getVarbitId()); - - // Only log when varbit value changes - if (varbitValue != lastVarbitValue) { - Microbot.log("Seaweed patch varbit value changed: " + lastVarbitValue + " -> " + varbitValue); - lastVarbitValue = varbitValue; - } - - // Official RuneLite varbit ranges for SEAWEED patches from PatchImplementation.java - // Note: varbit 3 means fully raked (0 rakes remaining) so it's ready for planting - if (varbitValue == 3) { - return "Empty"; // Fully raked, ready for planting - } - - if ((varbitValue >= 0 && varbitValue <= 2) || (varbitValue >= 17 && varbitValue <= 255)) { - return "Weeds"; // Needs raking (0=full weeds, 1=partial, 2=almost done) - } - - if (varbitValue >= 4 && varbitValue <= 7) { - return "Growing"; - } - - if (varbitValue >= 8 && varbitValue <= 10) { - return "Harvestable"; - } - - if (varbitValue >= 11 && varbitValue <= 13) { - return "Diseased"; - } - - if (varbitValue >= 14 && varbitValue <= 16) { - return "Dead"; // Needs clearing - Dead seaweed objects 30497,30498,30499 - } - - return "Empty"; + private static String getPatchState(Rs2TileObjectModel objModel) { + if (objModel == null) return "Unknown"; + ObjectComposition comp = objModel.getObjectComposition(); + if (comp == null) return "Unknown"; + String[] actions = comp.getActions(); + if (actions == null) return "Unknown"; + for (String action : actions) { + if (action == null) continue; + if (action.equalsIgnoreCase("Pick")) return "Harvestable"; + if (action.equalsIgnoreCase("Cure")) return "Diseased"; + if (action.equalsIgnoreCase("Clear")) return "Dead"; + if (action.equalsIgnoreCase("Rake")) return "Weeds"; + } + String name = comp.getName(); + if (name != null && name.equalsIgnoreCase("Seaweed patch")) return "Empty"; + if (name != null && name.equalsIgnoreCase("Seaweed")) return "Growing"; + return "Unknown"; } @@ -301,7 +280,7 @@ private void handleBanking() { } private void handleDiving() { - Rs2GameObject.interact(BOAT, "Dive"); + Microbot.getRs2TileObjectCache().query().interact(BOAT, "Dive"); sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() == 1, 5000); if (Rs2Player.getWorldLocation().getPlane() != 1) { Microbot.log("We failed to get underwater - Make sure to handle the warning dialog manually once"); @@ -351,11 +330,11 @@ private void handleFarmNull(){ } private void handleNoting(){ - Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool leprechaun"); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearestOnClientThread(); if (leprechaun == null) {return;} Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Giant seaweed", true); Rs2Inventory.use(unNoted); - Rs2Npc.interact(leprechaun, "Talk-to"); + leprechaun.click("Talk-to"); Rs2Inventory.waitForInventoryChanges(10000); } @@ -365,40 +344,52 @@ private boolean handlePatch(int patchId) { handleNoting(); } - Integer[] ids = { - patchId - }; - var obj = Rs2GameObject.findObject(ids); - - // If not found by ID, look for patch objects by name - if (obj == null) { - obj = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> { - var objComp = Rs2GameObject.convertToObjectComposition(o, false); - if (objComp == null || objComp.getName() == null) return false; - String name = objComp.getName(); - // Look for seaweed patch objects or dead seaweed - return name.equalsIgnoreCase("Dead seaweed") || - name.equalsIgnoreCase("Seaweed patch") || - (name.equalsIgnoreCase("Seaweed") && objComp.getId() == patchId); - }) - .findFirst() - .orElse(null); + var objModel = Microbot.getRs2TileObjectCache().query().withId(patchId).nearest(); + + if (objModel == null) { + objModel = Microbot.getRs2TileObjectCache().query().where(o -> { + var objComp = o.getObjectComposition(); + if (objComp == null || objComp.getName() == null) return false; + String name = objComp.getName(); + return name.equalsIgnoreCase("Dead seaweed") || + name.equalsIgnoreCase("Seaweed patch") || + (name.equalsIgnoreCase("Seaweed") && objComp.getId() == patchId); + }).nearest(); } - if (obj == null) return false; + if (objModel == null) return false; - // Make final reference for lambda usage - final var patchObj = obj; - var state = getSeaweedPatchState(patchObj); + final var patchObjModel = objModel; + var state = getPatchState(patchObjModel); logDebug("Patch state detected as: " + state); switch (state) { + case "Harvestable": + if (config.FarmingCape()) { + if (Rs2Inventory.contains("Farming cape") && !Rs2Equipment.isWearing("Farming cape")) { + Rs2Inventory.interact("Farming cape", "Wear"); + sleep(150, 250); + } + if (Rs2Inventory.contains("Farming cape(t)") && !Rs2Equipment.isWearing("Farming cape(t)")) { + Rs2Inventory.interact("Farming cape(t)", "Wear"); + sleep(150, 250); + } + } + + patchObjModel.click("Pick"); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Harvestable") || Rs2Inventory.isFull(), 20000); + + if (!Rs2Equipment.isWearing("Diving apparatus") && Rs2Inventory.contains("Diving apparatus")) { + Rs2Inventory.interact("Diving apparatus", "Wear"); + sleep(200, 300); + } + return false; + case "Weeds": + patchObjModel.click("Rake"); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Weeds"), 15000); + return false; case "Empty": - // Enter critical section to prevent spore looting interruption inCriticalSection = true; try { - // Verify we have materials before starting atomic operation boolean hasCompost = Rs2Inventory.contains("compost") || Rs2Inventory.contains("Supercompost") || Rs2Inventory.contains("Ultracompost") || @@ -406,89 +397,30 @@ private boolean handlePatch(int patchId) { if (hasCompost) { Rs2Inventory.use("compost"); - Rs2GameObject.interact(patchObj, "Compost"); + patchObjModel.click("Compost"); Rs2Player.waitForXpDrop(Skill.FARMING); } - // Always attempt planting if we have spores if (Rs2Inventory.contains("seaweed spore")) { Rs2Inventory.use(" spore"); - Rs2GameObject.interact(patchObj, "Plant"); - sleepUntil(() -> getSeaweedPatchState(patchObj).equals("Growing"), 10000); + patchObjModel.click("Plant"); + sleepUntil(() -> getPatchState(patchObjModel).equals("Growing"), 10000); } return true; } finally { - // Always release critical section, even if error occurs inCriticalSection = false; } - case "Harvestable": - - // EQUIP FARMING CAPE FOR HARVEST BONUS - if (config.FarmingCape()) { - if (Rs2Inventory.contains("Farming cape") && !Rs2Equipment.isWearing("Farming cape")) { - Rs2Inventory.interact("Farming cape", "Wear"); - sleep(150, 250); - } - if (Rs2Inventory.contains("Farming cape(t)") && !Rs2Equipment.isWearing("Farming cape(t)")) { - Rs2Inventory.interact("Farming cape(t)", "Wear"); - sleep(150, 250); - } - } - - Rs2GameObject.interact(patchObj, "Pick"); - sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); - // Harvesting is complete when patch becomes empty or inventory is full - return currentState.equals("Empty") || Rs2Inventory.isFull(); - }, 20000); - - // IMMEDIATELY RE-EQUIP DIVING APPARATUS - if (!Rs2Equipment.isWearing("Diving apparatus") && Rs2Inventory.contains("Diving apparatus")) { - Rs2Inventory.interact("Diving apparatus", "Wear"); - sleep(200, 300); - } - return false; // Don't mark as handled - needs planting after harvesting - case "Weeds": - Rs2GameObject.interact(patchObj, "Rake"); - sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); - return !currentState.equals("Weeds"); - }, 10000); - return false; // Don't mark as handled - needs planting after raking case "Dead": - Rs2GameObject.interact(patchObj, "Clear"); - sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); - return !currentState.equals("Dead"); - }, 10000); - return false; // Don't mark as handled - needs planting after clearing + patchObjModel.click("Clear"); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Dead"), 10000); + return false; case "Diseased": Microbot.showMessage("Diseased patch! Please turn off the script and then cure me manually as i cant do this automatically yet."); return false; - default: - currentPatch = null; + case "Growing": return true; + default: + return false; } } @@ -498,7 +430,7 @@ private void returnToBank() { Rs2Walker.walkTo(UnderWaterAnchor); // Brief pause to allow spore detection before climbing sleep(300, 500); - Rs2GameObject.interact(UNDERWATER_ANCHOR, "Climb"); + Microbot.getRs2TileObjectCache().query().interact(UNDERWATER_ANCHOR, "Climb"); sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() == 0, 7000); } @@ -517,22 +449,6 @@ private void returnToBank() { } - private void safetyCheck() { - if (Rs2Player.getWorldLocation().getPlane() != 1) return; // only underwater - - if (!Rs2Equipment.isWearing("Diving apparatus")) { - // if > 2000 ms without apparatus underwater, force equip - if (System.currentTimeMillis() - lastApparatusCheck > 2000) { - if (Rs2Inventory.contains("Diving apparatus")) { - Rs2Inventory.interact("Diving apparatus", "Wear"); - Microbot.log("SAFETY: Diving apparatus re-equipped automatically!"); - } - } - } else { - lastApparatusCheck = System.currentTimeMillis(); - } - } - private void GSF_AntiBan_Setup(){ Microbot.enableAutoRunOn = false; Rs2Antiban.resetAntibanSettings(); @@ -558,15 +474,16 @@ private void logDebug(String msg) { } private void shutdownSequence(){ - this.shutdown(); + Microbot.stopPlugin(giantSeaweedPlugin); } @Override public void shutdown() { GSF_Running = false; BOT_STATE = GiantSeaweedFarmerStatus.IDLE; + BankSuccess = false; handledPatches = new ArrayList<>(); - inCriticalSection = false; // Ensure flag is cleared on shutdown + inCriticalSection = false; super.shutdown(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java index a11bb64b9d..cfacd06418 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java @@ -3,7 +3,6 @@ import net.runelite.api.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -20,7 +19,7 @@ public boolean run(GiantSeaweedFarmerConfig config) { if (!config.lootSeaweedSpores()) return; // Check for seaweed spores - but respect critical farming operations - if (Rs2GroundItem.exists(ItemID.SEAWEED_SPORE, 15) && + if (Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).count() > 0 && !GiantSeaweedFarmerScript.inCriticalSection) { // Pause all other scripts while we loot Microbot.pauseAllScripts.set(true); @@ -41,9 +40,9 @@ public boolean run(GiantSeaweedFarmerConfig config) { } private void lootAllSpores() { - while (Rs2GroundItem.exists(ItemID.SEAWEED_SPORE, 15) && this.isRunning()) { + while (Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).count() > 0 && this.isRunning()) { Microbot.log("Seaweed spore detected - looting"); - boolean looted = Rs2GroundItem.loot(ItemID.SEAWEED_SPORE, 15); + boolean looted = Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).interact("Take"); if (looted) { // Wait for movement to start and complete sleepUntil(Rs2Player::isMoving, 2000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java index d7fd97749a..134231e23d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java @@ -1,27 +1,131 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @ConfigGroup("hueyprayer") public interface HueyPrayerConfig extends Config { + @ConfigSection( + name = "General", + description = "Core Huey prayer behaviour", + position = 0 + ) + String generalSection = "general"; + + @ConfigSection( + name = "Trio mode", + description = "After a Huey projectile hits, set protection for pillar charging (fixed or autobalance)", + position = 1 + ) + String trioSection = "trio"; + @ConfigItem( keyName = "enabled", name = "Enable", - description = "Enable auto prayer" + description = "Enable auto prayer", + position = 0, + section = generalSection ) default boolean enabled() { return true; } + @ConfigItem( + keyName = "disableAfterImpact", + name = "Disable after impact", + description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them.", + position = 1, + section = generalSection + ) + default boolean disableAfterImpact() + { + return true; + } + @ConfigItem( keyName = "debug", name = "Debug Projectiles", - description = "Print projectile IDs" + description = "Print projectile IDs", + position = 2, + section = generalSection ) default boolean debug() { return false; } -} \ No newline at end of file + + @ConfigItem( + keyName = "trioMode", + name = "Trio mode", + description = "While incoming Huey projectiles still use the correct protect vs type, after impact (requires Disable after impact) switches to your pillar role: Fixed or Autobalance protection from teammates' overheads.", + position = 0, + section = trioSection + ) + default boolean trioMode() + { + return false; + } + + @ConfigItem( + keyName = "trioRoleStyle", + name = "Role style", + description = "Fixed: always use the protection prayer below. Autobalance: among other players in radius, count melee / missiles / magic overheads — you pray the least-covered protection.", + position = 1, + section = trioSection + ) + default TrioRoleStyle trioRoleStyle() + { + return TrioRoleStyle.FIXED; + } + + @ConfigItem( + keyName = "trioFixedProtection", + name = "Fixed protection", + description = "Used when Role style is Fixed.", + position = 2, + section = trioSection + ) + default TrioFixedProtection trioFixedProtection() + { + return TrioFixedProtection.PROTECT_RANGE; + } + + @ConfigItem( + keyName = "trioRadius", + name = "Autobalance radius", + description = "Tiles from your tile to include other players when autobalancing (they must still be loaded).", + position = 3, + section = trioSection + ) + default int trioRadius() + { + return 15; + } + + enum TrioRoleStyle + { + FIXED, + AUTOBALANCE + } + + enum TrioFixedProtection + { + PROTECT_MELEE(Rs2PrayerEnum.PROTECT_MELEE), + PROTECT_RANGE(Rs2PrayerEnum.PROTECT_RANGE), + PROTECT_MAGIC(Rs2PrayerEnum.PROTECT_MAGIC); + + private final Rs2PrayerEnum prayer; + + TrioFixedProtection(Rs2PrayerEnum prayer) + { + this.prayer = prayer; + } + + public Rs2PrayerEnum getPrayer() + { + return prayer; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java index 4d52102761..8b56dab221 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java @@ -1,4 +1,4 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java index 4ecf14ec96..b7ec7c9632 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java @@ -1,4 +1,4 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import com.google.inject.Provides; import javax.inject.Inject; @@ -6,7 +6,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.HeadIcon; +import net.runelite.api.Player; import net.runelite.api.Projectile; +import net.runelite.api.WorldView; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ProjectileMoved; @@ -17,25 +21,31 @@ import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.PluginConstants; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; @Slf4j @PluginDescriptor( - name = "Huey Prayer", + name = PluginConstants.DEFAULT_PREFIX + "Huey Prayer", description = "Auto prayer vs Hueycoatl (3-style projectile)", tags = {"microbot", "huey", "prayer"}, version = HueyPrayerPlugin.VERSION, - minClientVersion = "2.1.34" + minClientVersion = "2.1.34", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL ) public class HueyPrayerPlugin extends Plugin { - static final String VERSION = "1.0.0"; + static final String VERSION = "1.0.7"; @Inject private Client client; @Inject private HueyPrayerConfig config; - // 🔴 YOU MUST FILL THESE USING DEBUG private static final int MAGIC_PROJECTILE_ID = 2975; private static final int RANGE_PROJECTILE_ID = 2972; private static final int MELEE_PROJECTILE_ID = 2969; @@ -43,6 +53,10 @@ public class HueyPrayerPlugin extends Plugin private Rs2PrayerEnum currentPrayer = null; private int lastSwitchTick = -1; + /** Huey projectiles still in flight toward the player (identity-based). */ + private final Set incomingHueyProjectiles = + Collections.newSetFromMap(new IdentityHashMap<>()); + @Provides HueyPrayerConfig provideConfig(net.runelite.client.config.ConfigManager configManager) { @@ -60,26 +74,87 @@ protected void shutDown() { Rs2Prayer.disableAllPrayers(); currentPrayer = null; + incomingHueyProjectiles.clear(); } @Subscribe public void onProjectileMoved(ProjectileMoved event) { - if (!config.enabled()) return; + if (!config.enabled()) + { + return; + } Projectile projectile = event.getProjectile(); + if (projectile == null) + { + return; + } // Only react to projectiles targeting YOU if (projectile.getInteracting() != client.getLocalPlayer()) + { return; + } int id = projectile.getId(); + boolean isHueyProjectile = false; + if (id == MAGIC_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (id == RANGE_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (id == MELEE_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (!isHueyProjectile) + { + return; + } + + if (!config.disableAfterImpact()) + { + if (!incomingHueyProjectiles.isEmpty()) + { + incomingHueyProjectiles.clear(); + } + } + + if (config.disableAfterImpact()) + { + if (incomingHueyProjectiles.contains(projectile)) + { + if (projectile.getRemainingCycles() <= 0) + { + incomingHueyProjectiles.remove(projectile); + if (incomingHueyProjectiles.isEmpty()) + { + onHueyProjectilePhaseEnded(); + } + } + return; + } + } + + if (projectile.getRemainingCycles() <= 0) + { + return; + } if (config.debug()) { Microbot.log("Projectile ID: " + id); } + if (config.disableAfterImpact()) + { + incomingHueyProjectiles.add(projectile); + } + switch (id) { case MAGIC_PROJECTILE_ID: @@ -101,9 +176,16 @@ private void switchPrayer(Rs2PrayerEnum prayer) { int tick = client.getTickCount(); - // prevent spam + duplicate toggles - if (currentPrayer == prayer) return; - if (tick == lastSwitchTick) return; + if (Rs2Prayer.isPrayerActive(prayer)) + { + currentPrayer = prayer; + return; + } + + if (tick == lastSwitchTick) + { + return; + } Rs2Prayer.disableAllPrayers(); Rs2Prayer.toggle(prayer, true); @@ -111,4 +193,141 @@ private void switchPrayer(Rs2PrayerEnum prayer) currentPrayer = prayer; lastSwitchTick = tick; } + + /** + * Last Huey projectile toward us has landed — either clear prayers or switch to trio pillar protection. + */ + private void onHueyProjectilePhaseEnded() + { + if (config.trioMode()) + { + Rs2PrayerEnum pillar = resolveTrioProtectionPrayer(); + if (pillar != null) + { + switchPrayer(pillar); + } + return; + } + Rs2Prayer.disableAllPrayers(); + currentPrayer = null; + } + + /** + * Fixed or autobalance protection for pillar charging (after projectiles, not during). + */ + private Rs2PrayerEnum resolveTrioProtectionPrayer() + { + if (config.trioRoleStyle() == HueyPrayerConfig.TrioRoleStyle.FIXED) + { + return config.trioFixedProtection().getPrayer(); + } + return pickAutobalanceProtectionPrayer(); + } + + private Rs2PrayerEnum pickAutobalanceProtectionPrayer() + { + Player local = client.getLocalPlayer(); + if (local == null) + { + return null; + } + WorldPoint localPoint = local.getWorldLocation(); + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) + { + return null; + } + + int melee = 0; + int ranged = 0; + int magic = 0; + int radius = config.trioRadius(); + if (radius < 1) + { + radius = 1; + } + + for (Player p : worldView.players()) + { + if (p == null) + { + continue; + } + if (p == local) + { + continue; + } + WorldPoint wp = p.getWorldLocation(); + if (wp == null) + { + continue; + } + if (wp.distanceTo(localPoint) > radius) + { + continue; + } + HeadIcon overhead = p.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + melee++; + } + else if (overhead == HeadIcon.RANGED) + { + ranged++; + } + else if (overhead == HeadIcon.MAGIC) + { + magic++; + } + } + + if (melee == 0) + { + if (ranged == 0) + { + if (magic == 0) + { + return protectionMatchingLocalOverhead(local); + } + } + } + + int minCount = melee; + if (ranged < minCount) + { + minCount = ranged; + } + if (magic < minCount) + { + minCount = magic; + } + + if (melee == minCount) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (ranged == minCount) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + return Rs2PrayerEnum.PROTECT_MAGIC; + } + + private static Rs2PrayerEnum protectionMatchingLocalOverhead(Player local) + { + HeadIcon overhead = local.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (overhead == HeadIcon.RANGED) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + if (overhead == HeadIcon.MAGIC) + { + return Rs2PrayerEnum.PROTECT_MAGIC; + } + return Rs2PrayerEnum.PROTECT_MELEE; + } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java index 3c28f7f994..0f078012af 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java +++ b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java @@ -34,6 +34,10 @@ private PluginConstants() public static final String NATE = "[N] "; public static final String SYN = "[Syn] "; public static final String BIGL = "[BL] "; + public static final String PERT = "[P] "; + public static final String DV = "[DV] "; + public static final String RED_BRACKET = "[RB] "; + public static final String STKS = "[STKS] "; public static final boolean DEFAULT_ENABLED = false; public static final boolean IS_EXTERNAL = true; //test diff --git a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java index 1c547fabbf..9b5c47e0ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java @@ -5,8 +5,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; @@ -38,13 +37,13 @@ public boolean run(RoyalTitansConfig config, RoyalTitansScript script) { if (royalTitansScript.state.equals(RoyalTitansBotStatus.BANKING) || royalTitansScript.state.equals(RoyalTitansBotStatus.TRAVELLING) || royalTitansScript.state.equals(RoyalTitansBotStatus.WAITING)) return; if (!isInBossRegion()) return; - var iceTitanDead = Rs2Npc.getNpcs(ICE_TITAN_DEAD_ID).findFirst().orElse(null); - var fireTitanDead = Rs2Npc.getNpcs(FIRE_TITAN_DEAD_ID).findFirst().orElse(null); - var iceTitan = Rs2Npc.getNpcs(ICE_TITAN_ID).findFirst().orElse(null); - var fireTitan = Rs2Npc.getNpcs(FIRE_TITAN_ID).findFirst().orElse(null); + var iceTitanDead = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_DEAD_ID).nearest(); + var fireTitanDead = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_DEAD_ID).nearest(); + var iceTitan = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_ID).nearest(); + var fireTitan = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_ID).nearest(); boolean looted = false; // Only loot when the giants are dead to not obstruct the fight - if (iceTitan != null && !iceTitan.isDead() || fireTitan != null && !fireTitan.isDead()) { + if (iceTitan != null && !iceTitan.getNpc().isDead() || fireTitan != null && !fireTitan.getNpc().isDead()) { return; } // Both titans are dead, ensure prayer is off @@ -117,10 +116,11 @@ public boolean run(RoyalTitansConfig config, RoyalTitansScript script) { return true; } - private static boolean lootTitan(Rs2NpcModel iceTitanDead) { - Rs2Npc.interact(iceTitanDead, "Loot"); + private static boolean lootTitan(Rs2NpcModel titanDead) { + if (titanDead == null) return false; + titanDead.click("Loot"); sleepUntil(() -> !Rs2Player.isMoving(), 3200); - var looted = Rs2Npc.interact(iceTitanDead, "Loot"); + var looted = titanDead.click("Loot"); Rs2Player.waitForAnimation(1800); return looted; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java index 81831fd4a6..8596b33c60 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java @@ -11,12 +11,10 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -28,7 +26,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; + import static net.runelite.client.plugins.microbot.RoyalTitans.RoyalTitansShared.evaluateAndConsumePotions; import static net.runelite.client.plugins.microbot.RoyalTitans.RoyalTitansShared.*; @@ -239,8 +237,8 @@ private boolean handleEscaping(RoyalTitansConfig config) { if ((noFood && currentHealth <= config.healthThreshold()) || (noPrayerPotions && currentPrayer < 10)) { shouldLeave = true; } - var iceTitanDead = Rs2Npc.getNpcs(ICE_TITAN_DEAD_ID).findFirst().orElse(null); - var fireTitanDead = Rs2Npc.getNpcs(FIRE_TITAN_DEAD_ID).findFirst().orElse(null); + var iceTitanDead = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_DEAD_ID).nearest(); + var fireTitanDead = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_DEAD_ID).nearest(); if (shouldLeave && iceTitanDead != null && fireTitanDead != null && !LootedTitanLastIteration) { Microbot.log("We want to escape, but Titans are dead, lets loot first"); } @@ -251,7 +249,7 @@ private boolean handleEscaping(RoyalTitansConfig config) { Rs2Player.waitForAnimation(1200); } else { enrageTile = null; - Rs2GameObject.interact(TUNNEL_ID_ESCAPE, "Quick-escape"); + Microbot.getRs2TileObjectCache().query().withId(TUNNEL_ID_ESCAPE).interact("Quick-escape"); Rs2Bank.walkToBank(); } state = RoyalTitansBotStatus.TRAVELLING; @@ -269,7 +267,7 @@ private void handlePrayers(RoyalTitansConfig config) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); return; } - if (Rs2Npc.getNpcs().anyMatch(x -> x.getId() == ICE_TITAN_ID || x.getId() == FIRE_TITAN_ID)) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getId() == ICE_TITAN_ID || x.getId() == FIRE_TITAN_ID).count() > 0) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); return; } @@ -302,8 +300,8 @@ private void handleOffensivePrayers(RoyalTitansConfig config) { } private void attackBoss(RoyalTitansConfig config) { - var iceTitan = Rs2Npc.getNpcs(ICE_TITAN_ID).findFirst().orElse(null); - var fireTitan = Rs2Npc.getNpcs(FIRE_TITAN_ID).findFirst().orElse(null); + var iceTitan = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_ID).nearest(); + var fireTitan = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_ID).nearest(); if (iceTitan == null && fireTitan == null) { Microbot.log("No titans found"); return; @@ -320,7 +318,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { if (specEnergy < config.specEnergyConsumed()) { return; } - if (titan == null || titan.isDead()) { + if (titan == null || titan.getNpc().isDead()) { return; } // Failsafe to handle special attack weapons that require to unequip 2 items @@ -337,7 +335,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { specialAttackInventorySetup.wearEquipment(); Rs2Combat.setSpecState(true, config.specEnergyConsumed() * 10); sleepUntil(Rs2Combat::getSpecState); - Rs2Npc.attack(titan); + titan.click("Attack"); Rs2Player.waitForAnimation(600); return; } @@ -345,7 +343,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { specialAttackInventorySetup.wearEquipment(); Rs2Combat.setSpecState(true, config.specEnergyConsumed() * 10); sleepUntil(Rs2Combat::getSpecState); - Rs2Npc.attack(titan); + titan.click("Attack"); Rs2Player.waitForAnimation(600); } } @@ -359,23 +357,23 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } equipArmor(rangedInventorySetup); // Handle focus properly based on config - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && fireTitan.isDead()) { - Rs2Npc.attack(fireTitan); + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && fireTitan.getNpc().isDead()) { + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && iceTitan.isDead()) { - Rs2Npc.attack(iceTitan); + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && iceTitan.getNpc().isDead()) { + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } // Fallback if focused titan is dead - if (fireTitan != null && !fireTitan.isDead()) { - Rs2Npc.attack(fireTitan); + if (fireTitan != null && !fireTitan.getNpc().isDead()) { + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } else if (iceTitan != null) { - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } @@ -385,7 +383,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 if (config.soloMode()) { subState = "Solo mode - balancing titan health"; Rs2NpcModel targetTitan = selectTitanForSoloMode(iceTitan, fireTitan); - if (targetTitan != null && !targetTitan.isDead()) { + if (targetTitan != null && !targetTitan.getNpc().isDead()) { int titanX = targetTitan.getWorldLocation().getRegionX(); // Select appropriate gear based on titan position @@ -400,7 +398,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 // Don't try to attack a Titan if enragetile is active and we are wearing melee armor if (!(enrageTile != null && meleeInventorySetup.doesEquipmentMatch())) { - Rs2Npc.attack(targetTitan); + targetTitan.click("Attack"); handleSpecialAttacks(config, targetTitan); } } @@ -408,7 +406,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } // Both bosses alive - Handle focus - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.isDead()) { + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -417,13 +415,13 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } // Both bosses alive - Handle focus - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.isDead()) { + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -432,10 +430,10 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; - } else if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && !iceTitan.isDead()) { + } else if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && !iceTitan.getNpc().isDead()) { subState = "Attacking ice titan"; int iceX = iceTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (iceX == MELEE_TITAN_ICE_REGION_X || @@ -448,12 +446,12 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 if (enrageTile != null && meleeInventorySetup.doesEquipmentMatch()) { return; } - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } // Only one boss alive - if (iceTitan != null && !iceTitan.isDead()) { + if (iceTitan != null && !iceTitan.getNpc().isDead()) { subState = "Only 1 boss alive, attacking ice titan"; int iceX = iceTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (iceX == MELEE_TITAN_ICE_REGION_X || @@ -462,11 +460,11 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } - if (fireTitan != null && !fireTitan.isDead()) { + if (fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Only 1 boss alive, attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -475,7 +473,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); } } @@ -489,14 +487,14 @@ private boolean handleWalls(RoyalTitansConfig config) { // For solo mode, handle both types of walls List walls; if (config.soloMode()) { - List fireWalls = Rs2Npc.getNpcs(FIRE_WALL).collect(Collectors.toList()); - List iceWalls = Rs2Npc.getNpcs(ICE_WALL).collect(Collectors.toList()); + List fireWalls = Microbot.getRs2NpcCache().query().withId(FIRE_WALL).toList(); + List iceWalls = Microbot.getRs2NpcCache().query().withId(ICE_WALL).toList(); walls = new ArrayList<>(); walls.addAll(fireWalls); walls.addAll(iceWalls); } else { - walls = Rs2Npc.getNpcs(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_WALL : ICE_WALL) - .collect(Collectors.toList()); + walls = Microbot.getRs2NpcCache().query().withId(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_WALL : ICE_WALL) + .toList(); } if (walls.isEmpty() || walls.size() < 8) { @@ -505,9 +503,9 @@ private boolean handleWalls(RoyalTitansConfig config) { equipArmor(magicInventorySetup); for (var wall : walls) { - if (wall != null && wall.getId() != -1 && !wall.isDead()) { + if (wall != null && wall.getId() != -1 && !wall.getNpc().isDead()) { String action = wall.getId() == FIRE_WALL ? "Douse" : "Melt"; - Rs2Npc.interact(wall, action); + wall.click(action); } } @@ -523,14 +521,14 @@ private boolean handleMinions(RoyalTitansConfig config) { // For solo mode, handle both types of minions List minions; if (config.soloMode()) { - List fireMinions = Rs2Npc.getNpcs(FIRE_MINION_ID).collect(Collectors.toList()); - List iceMinions = Rs2Npc.getNpcs(ICE_MINION_ID).collect(Collectors.toList()); + List fireMinions = Microbot.getRs2NpcCache().query().withId(FIRE_MINION_ID).toList(); + List iceMinions = Microbot.getRs2NpcCache().query().withId(ICE_MINION_ID).toList(); minions = new ArrayList<>(); minions.addAll(fireMinions); minions.addAll(iceMinions); } else { - minions = Rs2Npc.getNpcs(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_MINION_ID : ICE_MINION_ID) - .collect(Collectors.toList()); + minions = Microbot.getRs2NpcCache().query().withId(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_MINION_ID : ICE_MINION_ID) + .toList(); } if (minions.isEmpty()) { @@ -539,8 +537,8 @@ private boolean handleMinions(RoyalTitansConfig config) { equipArmor(magicInventorySetup); for (var minion : minions) { - if (minion != null && !minion.isDead()) { - Rs2Npc.attack(minion); + if (minion != null && !minion.getNpc().isDead()) { + minion.click("Attack"); } } @@ -548,12 +546,12 @@ private boolean handleMinions(RoyalTitansConfig config) { } private Rs2NpcModel selectTitanForSoloMode(Rs2NpcModel iceTitan, Rs2NpcModel fireTitan) { - if (iceTitan == null || iceTitan.isDead()) return fireTitan; - if (fireTitan == null || fireTitan.isDead()) return iceTitan; + if (iceTitan == null || iceTitan.getNpc().isDead()) return fireTitan; + if (fireTitan == null || fireTitan.getNpc().isDead()) return iceTitan; // Get health ratios - double iceHealthRatio = iceTitan.getHealthRatio(); - double fireHealthRatio = fireTitan.getHealthRatio(); + double iceHealthRatio = iceTitan.getNpc().getHealthRatio(); + double fireHealthRatio = fireTitan.getNpc().getHealthRatio(); // If one titan has significantly more health, attack that one // Using a 20% threshold to prevent frequent switching @@ -567,8 +565,8 @@ private Rs2NpcModel selectTitanForSoloMode(Rs2NpcModel iceTitan, Rs2NpcModel fir if (Rs2Player.isInCombat()) { var interacting = Microbot.getClient().getLocalPlayer().getInteracting(); - if (interacting == iceTitan) return iceTitan; - if (interacting == fireTitan) return fireTitan; + if (interacting == iceTitan.getNpc()) return iceTitan; + if (interacting == fireTitan.getNpc()) return fireTitan; } // Default: attack the one with slightly higher health @@ -704,7 +702,7 @@ private void handleTravelling(RoyalTitansConfig config) { sleep(1200, 1600); } } else { - Rs2GameObject.interact(TUNNEL_ID, "Enter"); + Microbot.getRs2TileObjectCache().query().withId(TUNNEL_ID).interact("Enter"); } break; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java b/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java new file mode 100644 index 0000000000..7ea9943381 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java @@ -0,0 +1,91 @@ +package net.runelite.client.plugins.microbot; + +import net.runelite.api.gameval.ItemID; +import net.runelite.api.widgets.Widget; +import net.runelite.client.plugins.microbot.util.Global; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import java.awt.event.KeyEvent; +import java.util.HashMap; +import java.util.Map; + +/** + * Shared utility for interacting with the Tool Leprechaun's exchange interface. + * Included in every plugin JAR via the build system, alongside PluginConstants. + */ +public final class Rs2Leprechaun { + + private static final int EXCHANGE_GROUP = 125; + private static final int EXCHANGE_ROOT_CHILD = 0; + private static final Map COMPOST_WIDGET_CHILDREN = new HashMap<>(); + + static { + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_COMPOST, 17); + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_SUPERCOMPOST, 18); + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_ULTRACOMPOST, 19); + } + + private Rs2Leprechaun() { + throw new UnsupportedOperationException("Utility class"); + } + + public static boolean isExchangeOpen() { + return Rs2Widget.isWidgetVisible(EXCHANGE_GROUP, EXCHANGE_ROOT_CHILD); + } + + public static boolean openExchange() { + Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool Leprechaun"); + if (leprechaun == null) { + Microbot.log("Tool Leprechaun not found nearby."); + return false; + } + Rs2Npc.interact(leprechaun, "Exchange"); + Global.sleepUntil(Rs2Leprechaun::isExchangeOpen, 5000); + return isExchangeOpen(); + } + + public static void closeExchange() { + if (isExchangeOpen()) { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + Global.sleepUntil(() -> !isExchangeOpen(), 2000); + } + } + + /** + * Withdraws one compost of the given type from the nearest Tool Leprechaun. + * Opens the exchange interface, clicks the compost widget (Remove-1), then closes. + * + * @param compostItemId one of ItemID.BUCKET_COMPOST, BUCKET_SUPERCOMPOST, or BUCKET_ULTRACOMPOST + * @return true if the compost appeared in inventory + */ + public static boolean withdrawCompost(int compostItemId) { + Integer childId = COMPOST_WIDGET_CHILDREN.get(compostItemId); + if (childId == null) { + Microbot.log("Unsupported compost item ID for leprechaun withdrawal: " + compostItemId); + return false; + } + + if (!isExchangeOpen() && !openExchange()) { + return false; + } + Global.sleep(300, 600); + + Widget compostWidget = Rs2Widget.getWidget(EXCHANGE_GROUP, childId); + if (compostWidget == null) { + Microbot.log("Compost widget not found in leprechaun interface."); + closeExchange(); + return false; + } + + Rs2Widget.clickWidget(compostWidget); + Global.sleepUntil(() -> Rs2Inventory.hasItem(compostItemId), 3000); + Global.sleep(300, 600); + + closeExchange(); + return Rs2Inventory.hasItem(compostItemId); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java b/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java index b34067f8aa..ebf38226b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java @@ -14,7 +14,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -160,7 +159,7 @@ private void enterMinigame() { Microbot.log("Unequipping shield..."); Rs2Equipment.unEquip(EquipmentInventorySlot.SHIELD); } - Rs2GameObject.interact(JUNA, "Story"); + Microbot.getRs2TileObjectCache().query().interact(JUNA, "Story"); } while (Rs2Dialogue.hasContinue() && this.isRunning()) { Rs2Dialogue.clickContinue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java index 9e18b02481..4759c3e2ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java @@ -25,7 +25,7 @@ ) public class TzhaarVenatorBowPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java index 13c274a0e0..afbd3e51f3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java @@ -11,12 +11,11 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.grandexchange.Rs2GrandExchange; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -152,7 +151,7 @@ private boolean EnsureMagerSafety() { var invalidNpcs = getInvalidNpcs(); if (!invalidNpcs.isEmpty()) { Microbot.log("Under attack from mager, focusing it"); - Rs2Npc.attack(invalidNpcs.get(0)); + invalidNpcs.get(0).click("Attack"); return false; } return true; @@ -169,27 +168,31 @@ private void InitiateCombat() { if (!hursList.isEmpty()) { // Attack the first TzHaar-Hur - Rs2Npc.attack(hursList.get(0)); + hursList.get(0).click("Attack"); } else { // Attack the first valid NPC - Rs2Npc.attack(npcs.get(0)); + npcs.get(0).click("Attack"); } } } } private List getValidNpcs() { - return Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6) - .filter(npc -> VALID_NPCS.contains(npc.getName())) - .filter(npc -> !INVALID_NPCS.contains(npc.getName())).collect(Collectors.toList()); + return Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6) + .where(npc -> VALID_NPCS.contains(npc.getName())) + .where(npc -> !INVALID_NPCS.contains(npc.getName())) + .toListOnClientThread(); } private List getInvalidNpcs() { - return Rs2Npc.getNpcsForPlayer("TzHaar-Mej").stream() - .filter(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 15) - .filter(npc -> !npc.isDead() && npc.isInteracting()) - .filter(npc -> Rs2Npc.hasLineOfSight(npc)).collect(Collectors.toList()); + return Microbot.getRs2NpcCache().query() + .withName("TzHaar-Mej") + .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 15) + .where(npc -> !npc.isDead() && npc.isInteracting()) + .where(npc -> npc.hasLineOfSight()) + .toListOnClientThread(); } private void handleTravel(TzHaarVenatorBowConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java new file mode 100644 index 0000000000..48253edc8d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; + +@ConfigGroup(ActionReplayConfig.GROUP) +@ConfigInformation( + "Enable the plugin, then open the panel.
" + + "Hit Start recording, do the actions in-game, then stop.
" + + "Play back anytime from the panel.
" + + "
" + + "Developed by Red Bracket. Feel free to make improvements,
" + + "just try to keep it simple :)" +) +public interface ActionReplayConfig extends Config +{ + String GROUP = "actionReplay"; +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java new file mode 100644 index 0000000000..cae7e3f8d7 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java @@ -0,0 +1,74 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; + +import javax.inject.Inject; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +public class ActionReplayOverlay extends Overlay +{ + private static final int DOT_RADIUS = 6; + private static final Color RECORD_COLOR = new Color(220, 40, 40); + private static final Color PLAY_COLOR = new Color(40, 200, 80); + + private final ActionReplayPlugin plugin; + + @Inject + public ActionReplayOverlay(ActionReplayPlugin plugin) + { + this.plugin = plugin; + setPosition(OverlayPosition.TOP_RIGHT); + setLayer(OverlayLayer.ABOVE_WIDGETS); + setPriority(OverlayPriority.HIGH); + } + + @Override + public Dimension render(Graphics2D g) + { + boolean recording = plugin.isRecording(); + boolean playing = plugin.isPlaying(); + if (!recording && !playing) + { + return null; + } + + boolean blink = ((System.currentTimeMillis() / 500) % 2) == 0; + Color color = recording ? RECORD_COLOR : PLAY_COLOR; + String label = recording ? "AIO AIO REC" : "AIO AIO PLAY"; + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + g.setFont(FontManager.getRunescapeSmallFont()); + int labelWidth = g.getFontMetrics().stringWidth(label); + int textBaseline = g.getFontMetrics().getAscent(); + int height = Math.max(DOT_RADIUS * 2 + 2, textBaseline + 2); + int width = DOT_RADIUS * 2 + 6 + labelWidth; + + int dotY = (height - DOT_RADIUS * 2) / 2; + if (blink || playing) + { + g.setColor(color); + g.fillOval(0, dotY, DOT_RADIUS * 2, DOT_RADIUS * 2); + } + g.setColor(color.darker()); + g.setStroke(new BasicStroke(1f)); + g.drawOval(0, dotY, DOT_RADIUS * 2, DOT_RADIUS * 2); + + int textX = DOT_RADIUS * 2 + 6; + int textY = (height + textBaseline) / 2 - 1; + g.setColor(Color.BLACK); + g.drawString(label, textX + 1, textY + 1); + g.setColor(Color.WHITE); + g.drawString(label, textX, textY); + + return new Dimension(width, height); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java new file mode 100644 index 0000000000..3e3e8193ef --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java @@ -0,0 +1,818 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.plugins.microbot.actionreplay.model.Condition; +import net.runelite.client.plugins.microbot.actionreplay.model.ConditionComparator; +import net.runelite.client.plugins.microbot.actionreplay.model.ConditionType; +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.StatKind; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; + +import javax.inject.Inject; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ActionReplayPanel extends PluginPanel +{ + private static final Color MUTED = new Color(160, 160, 160); + private static final String NO_SCRIPTS_PLACEHOLDER = "(no scripts)"; + + private ActionReplayPlugin plugin; + + private final JLabel countLabel = new JLabel(" "); + private final JButton recordButton = new JButton("● Record script"); + private final JButton appendButton = new JButton("⊕"); + private final JButton stopPlaybackButton = new JButton("■ Stop script"); + + private final JComboBox scriptSelector = new JComboBox<>(); + private final DefaultListModel actionsModel = new DefaultListModel<>(); + private final JList actionsList = new JList<>(actionsModel); + + private final JButton upButton = new JButton("↑"); + private final JButton downButton = new JButton("↓"); + private final JButton deleteStepButton = new JButton("✕"); + private final JButton editStepButton = new JButton("✎"); + private final JButton playButton = new JButton("▶ Run script"); + private final JButton renameButton = new JButton("✎ Rename"); + private final JButton deleteScriptButton = new JButton("🗑 Delete"); + + private Recording viewedRecording; + private List savedRecordings = new ArrayList<>(); + private boolean suppressSelectorEvents = false; + + @Inject + public ActionReplayPanel() + { + super(false); + setBorder(new EmptyBorder(10, 10, 10, 10)); + setLayout(new BorderLayout(0, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + add(buildHeader(), BorderLayout.NORTH); + add(buildCenter(), BorderLayout.CENTER); + } + + private JPanel buildHeader() + { + JPanel header = new JPanel(); + header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS)); + header.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel title = new JLabel("AIO AIO"); + title.setFont(FontManager.getRunescapeBoldFont()); + title.setForeground(Color.WHITE); + title.setAlignmentX(Component.LEFT_ALIGNMENT); + header.add(title); + header.add(Box.createVerticalStrut(6)); + + countLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + header.add(countLabel); + + header.add(Box.createVerticalStrut(8)); + + recordButton.setAlignmentX(Component.LEFT_ALIGNMENT); + recordButton.setForeground(Color.WHITE); + recordButton.addActionListener(e -> onRecordClicked()); + + playButton.setAlignmentX(Component.LEFT_ALIGNMENT); + playButton.setForeground(Color.WHITE); + playButton.setToolTipText("Loop selected script until stopped"); + playButton.addActionListener(e -> onPlay()); + + stopPlaybackButton.setAlignmentX(Component.LEFT_ALIGNMENT); + stopPlaybackButton.setForeground(Color.WHITE); + stopPlaybackButton.addActionListener(e -> plugin.stopPlayback()); + + header.add(playButton); + header.add(Box.createVerticalStrut(4)); + header.add(stopPlaybackButton); + header.add(Box.createVerticalStrut(4)); + header.add(recordButton); + + return header; + } + + private JPanel buildCenter() + { + JPanel center = new JPanel(); + center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS)); + center.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel selectorLabel = new JLabel("Script:"); + selectorLabel.setForeground(MUTED); + selectorLabel.setFont(selectorLabel.getFont().deriveFont(11f)); + selectorLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(selectorLabel); + center.add(Box.createVerticalStrut(2)); + + scriptSelector.setAlignmentX(Component.LEFT_ALIGNMENT); + scriptSelector.setMaximumSize(new Dimension(Integer.MAX_VALUE, 24)); + scriptSelector.addActionListener(e -> + { + if (!suppressSelectorEvents) + { + onSelectorChanged(); + } + }); + center.add(scriptSelector); + center.add(Box.createVerticalStrut(4)); + + actionsList.setBackground(ColorScheme.DARKER_GRAY_COLOR); + actionsList.setForeground(Color.WHITE); + actionsList.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + if (e.getClickCount() == 2) + { + int idx = actionsList.locationToIndex(e.getPoint()); + if (idx >= 0) + { + actionsList.setSelectedIndex(idx); + onEditStep(); + } + } + } + }); + JScrollPane scroll = new JScrollPane(actionsList); + scroll.setPreferredSize(new Dimension(220, 200)); + scroll.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); + scroll.setAlignmentX(Component.LEFT_ALIGNMENT); + scroll.setBorder(null); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + center.add(scroll); + center.add(Box.createVerticalStrut(6)); + + JPanel editRow = new JPanel(new GridLayout(1, 5, 3, 0)); + editRow.setOpaque(false); + editRow.setAlignmentX(Component.LEFT_ALIGNMENT); + editRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 28)); + + initSmallButton(upButton, "Move step up"); + upButton.addActionListener(e -> onMoveUp()); + editRow.add(upButton); + + initSmallButton(downButton, "Move step down"); + downButton.addActionListener(e -> onMoveDown()); + editRow.add(downButton); + + initSmallButton(editStepButton, "Edit step (double-click works too)"); + editStepButton.addActionListener(e -> onEditStep()); + editRow.add(editStepButton); + + initSmallButton(appendButton, "Append more actions to the selected script"); + appendButton.addActionListener(e -> onAppendClicked()); + editRow.add(appendButton); + + initSmallButton(deleteStepButton, "Delete selected step(s)"); + deleteStepButton.addActionListener(e -> onDeleteStep()); + editRow.add(deleteStepButton); + + center.add(editRow); + center.add(Box.createVerticalStrut(6)); + + JPanel scriptRow = new JPanel(new GridLayout(1, 2, 4, 4)); + scriptRow.setOpaque(false); + scriptRow.setAlignmentX(Component.LEFT_ALIGNMENT); + scriptRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32)); + + renameButton.setToolTipText("Rename selected script"); + renameButton.addActionListener(e -> onRename()); + scriptRow.add(renameButton); + + deleteScriptButton.setToolTipText("Delete selected script"); + deleteScriptButton.addActionListener(e -> onDeleteScript()); + scriptRow.add(deleteScriptButton); + + center.add(scriptRow); + + return center; + } + + private void initSmallButton(JButton b, String tooltip) + { + b.setToolTipText(tooltip); + b.setMargin(new java.awt.Insets(2, 4, 2, 4)); + b.setFocusPainted(false); + } + + public void setPlugin(ActionReplayPlugin plugin) + { + this.plugin = plugin; + } + + public void refresh() + { + if (plugin == null) + { + return; + } + SwingUtilities.invokeLater(() -> + { + boolean rec = plugin.isRecording(); + boolean play = plugin.isPlaying(); + boolean appending = plugin.isAppendingToExisting(); + + if (rec) + { + countLabel.setText("Captured: " + plugin.getCurrentRecordingSize() + " actions"); + countLabel.setForeground(MUTED); + } + else + { + countLabel.setText(" "); + } + + stopPlaybackButton.setEnabled(play); + + refreshSelector(); + reloadActionList(); + updateButtonEnabled(rec, play, appending); + + revalidate(); + repaint(); + }); + } + + private void updateButtonEnabled(boolean rec, boolean play, boolean appending) + { + Recording selected = getSelectedRecording(); + boolean hasActions = selected != null && selected.size() > 0; + boolean canEditSteps = !rec && !play && hasActions; + + upButton.setEnabled(canEditSteps); + downButton.setEnabled(canEditSteps); + deleteStepButton.setEnabled(canEditSteps); + editStepButton.setEnabled(canEditSteps); + + playButton.setEnabled(!rec && !play && hasActions); + renameButton.setEnabled(!rec && !play && viewedRecording != null); + deleteScriptButton.setEnabled(!rec && !play && viewedRecording != null); + + scriptSelector.setEnabled(!rec && !savedRecordings.isEmpty()); + + if (rec && !appending) + { + recordButton.setText("■ Stop recording"); + recordButton.setEnabled(true); + appendButton.setText("⊕"); + appendButton.setEnabled(false); + } + else if (rec) + { + recordButton.setText("● Record script"); + recordButton.setEnabled(false); + appendButton.setText("■"); + appendButton.setEnabled(true); + } + else + { + recordButton.setText("● Record script"); + recordButton.setEnabled(!play); + appendButton.setText("⊕"); + appendButton.setEnabled(!play && viewedRecording != null); + } + } + + private void refreshSelector() + { + suppressSelectorEvents = true; + try + { + savedRecordings = plugin.listRecordings(); + scriptSelector.removeAllItems(); + + if (savedRecordings.isEmpty()) + { + scriptSelector.addItem(NO_SCRIPTS_PLACEHOLDER); + scriptSelector.setSelectedIndex(0); + viewedRecording = null; + return; + } + + for (Recording r : savedRecordings) + { + scriptSelector.addItem(r.getName()); + } + + String targetName = viewedRecording != null ? viewedRecording.getName() : null; + int selectedIdx = 0; + if (targetName != null) + { + for (int i = 0; i < savedRecordings.size(); i++) + { + if (targetName.equals(savedRecordings.get(i).getName())) + { + selectedIdx = i; + break; + } + } + } + scriptSelector.setSelectedIndex(selectedIdx); + viewedRecording = savedRecordings.get(selectedIdx); + } + finally + { + suppressSelectorEvents = false; + } + } + + private void onSelectorChanged() + { + int idx = scriptSelector.getSelectedIndex(); + if (idx >= 0 && idx < savedRecordings.size()) + { + viewedRecording = savedRecordings.get(idx); + } + else + { + viewedRecording = null; + } + reloadActionList(); + updateButtonEnabled(plugin.isRecording(), plugin.isPlaying(), plugin.isAppendingToExisting()); + } + + private Recording getSelectedRecording() + { + if (plugin.isRecording()) + { + return plugin.getCurrentRecording(); + } + return viewedRecording; + } + + private void reloadActionList() + { + int prevSelected = actionsList.getSelectedIndex(); + Recording r = getSelectedRecording(); + actionsModel.clear(); + if (r == null || r.getActions() == null || r.getActions().isEmpty()) + { + return; + } + for (int i = 0; i < r.getActions().size(); i++) + { + actionsModel.addElement(format(i, r.getActions().get(i))); + } + if (prevSelected >= 0 && prevSelected < actionsModel.size()) + { + actionsList.setSelectedIndex(prevSelected); + } + } + + public void onActionRecorded(RecordedAction action) + { + SwingUtilities.invokeLater(() -> + { + if (plugin.isRecording()) + { + int idx = actionsModel.size(); + actionsModel.addElement(format(idx, action)); + actionsList.ensureIndexIsVisible(idx); + } + countLabel.setText("Captured: " + plugin.getCurrentRecordingSize() + " actions"); + countLabel.setForeground(MUTED); + }); + } + + private void onRecordClicked() + { + if (plugin.isRecording()) + { + Recording saved = plugin.stopRecording(true); + if (saved != null) + { + viewedRecording = saved; + } + refresh(); + } + else + { + actionsModel.clear(); + viewedRecording = null; + plugin.startRecording(null); + refresh(); + } + } + + private void onAppendClicked() + { + if (plugin.isRecording()) + { + plugin.stopRecording(true); + refresh(); + } + else + { + if (viewedRecording == null) + { + return; + } + plugin.startRecording(viewedRecording); + refresh(); + } + } + + private void onMoveUp() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx <= 0) + { + return; + } + List actions = r.getActions(); + RecordedAction moved = actions.remove(idx); + actions.add(idx - 1, moved); + reloadActionList(); + actionsList.setSelectedIndex(idx - 1); + persistIfSaved(r); + } + + private void onMoveDown() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx < 0 || idx >= r.getActions().size() - 1) + { + return; + } + List actions = r.getActions(); + RecordedAction moved = actions.remove(idx); + actions.add(idx + 1, moved); + reloadActionList(); + actionsList.setSelectedIndex(idx + 1); + persistIfSaved(r); + } + + private void onDeleteStep() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int[] selected = actionsList.getSelectedIndices(); + if (selected.length == 0) + { + return; + } + for (int i = selected.length - 1; i >= 0; i--) + { + if (selected[i] >= 0 && selected[i] < r.getActions().size()) + { + r.getActions().remove(selected[i]); + } + } + reloadActionList(); + persistIfSaved(r); + } + + private void onEditStep() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx < 0 || idx >= r.getActions().size()) + { + return; + } + RecordedAction a = r.getActions().get(idx); + + int currentTicks = a.getDelayTicksBefore() != null ? a.getDelayTicksBefore() : 0; + SpinnerNumberModel spinnerModel = new SpinnerNumberModel(currentTicks, 0, 1000, 1); + JSpinner ticksSpinner = new JSpinner(spinnerModel); + + String[] typeOptions = {"None", "HP", "Prayer", "NPC nearby", "Object nearby", "Inventory"}; + JComboBox typeCombo = new JComboBox<>(typeOptions); + JComboBox statCmpCombo = new JComboBox<>(new String[]{"below", "above"}); + JSpinner statSpinner = new JSpinner(new SpinnerNumberModel(20, 0, 9999, 1)); + JTextField npcNameField = new JTextField(15); + JComboBox npcPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + JTextField objNameField = new JTextField(15); + JComboBox objPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + JTextField invNameField = new JTextField(15); + JSpinner invCountSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 9999, 1)); + JComboBox invPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + + Condition existing = a.getCondition(); + if (existing != null && existing.getType() != null) + { + switch (existing.getType()) + { + case STAT: + typeCombo.setSelectedItem(existing.getStat() == StatKind.PRAYER ? "Prayer" : "HP"); + statCmpCombo.setSelectedItem(existing.getComparator() == ConditionComparator.ABOVE ? "above" : "below"); + if (existing.getThreshold() != null) statSpinner.setValue(existing.getThreshold()); + break; + case NPC_NEARBY: + typeCombo.setSelectedItem("NPC nearby"); + if (existing.getName() != null) npcNameField.setText(existing.getName()); + npcPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + case OBJECT_NEARBY: + typeCombo.setSelectedItem("Object nearby"); + if (existing.getName() != null) objNameField.setText(existing.getName()); + objPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + case INVENTORY: + typeCombo.setSelectedItem("Inventory"); + if (existing.getName() != null) invNameField.setText(existing.getName()); + if (existing.getMinCount() != null && existing.getMinCount() > 0) invCountSpinner.setValue(existing.getMinCount()); + invPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + } + } + + JPanel statPanel = hBox(statCmpCombo, statSpinner); + JPanel npcPanel = hBox(new JLabel("name:"), npcNameField, npcPresentCombo); + JPanel objPanel = hBox(new JLabel("name:"), objNameField, objPresentCombo); + JPanel invPanel = hBox(new JLabel("item:"), invNameField, new JLabel("min:"), invCountSpinner, invPresentCombo); + + CardLayout cardLayout = new CardLayout(); + JPanel cards = new JPanel(cardLayout); + cards.add(new JPanel(), "None"); + cards.add(statPanel, "Stat"); + cards.add(npcPanel, "NPC nearby"); + cards.add(objPanel, "Object nearby"); + cards.add(invPanel, "Inventory"); + + Runnable showCard = () -> + { + String sel = (String) typeCombo.getSelectedItem(); + String card; + if ("HP".equals(sel) || "Prayer".equals(sel)) card = "Stat"; + else if (sel == null || "None".equals(sel)) card = "None"; + else card = sel; + cardLayout.show(cards, card); + }; + typeCombo.addActionListener(e -> showCard.run()); + showCard.run(); + + JPanel form = new JPanel(new GridBagLayout()); + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(4, 4, 4, 4); + gc.anchor = GridBagConstraints.WEST; + + gc.gridx = 0; + JTextField verbField = new JTextField(a.getMenuOption() == null ? "" : a.getMenuOption(), 12); + JTextField targetField = new JTextField(a.getTargetName() == null ? "" : a.getTargetName(), 15); + + gc.gridy = 0; + form.add(new JLabel("Action:"), gc); + gc.gridx = 1; + form.add(verbField, gc); + + gc.gridx = 0; + gc.gridy = 1; + form.add(new JLabel("Target:"), gc); + gc.gridx = 1; + form.add(targetField, gc); + + gc.gridx = 0; + gc.gridy = 2; + form.add(new JLabel("Delay before (ticks):"), gc); + gc.gridx = 1; + form.add(ticksSpinner, gc); + + gc.gridx = 0; + gc.gridy = 3; + form.add(new JLabel("Condition:"), gc); + gc.gridx = 1; + form.add(typeCombo, gc); + + gc.gridx = 0; + gc.gridy = 4; + gc.gridwidth = 2; + form.add(cards, gc); + + Object[] options = {"OK", "Delete", "Cancel"}; + int choice = JOptionPane.showOptionDialog(this, form, "Edit step #" + (idx + 1), + JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); + if (choice == 1) + { + r.getActions().remove(idx); + reloadActionList(); + persistIfSaved(r); + return; + } + if (choice != 0) + { + return; + } + + int newTicks = (Integer) ticksSpinner.getValue(); + a.setDelayTicksBefore(newTicks); + String newVerb = verbField.getText().trim(); + a.setMenuOption(newVerb.isEmpty() ? null : newVerb); + String newTarget = targetField.getText().trim(); + a.setTargetName(newTarget.isEmpty() ? null : newTarget); + a.setCondition(buildCondition(typeCombo, statCmpCombo, statSpinner, npcNameField, npcPresentCombo, + objNameField, objPresentCombo, invNameField, invCountSpinner, invPresentCombo)); + reloadActionList(); + actionsList.setSelectedIndex(idx); + persistIfSaved(r); + } + + private static JPanel hBox(Component... components) + { + JPanel p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); + for (int i = 0; i < components.length; i++) + { + if (i > 0) p.add(Box.createHorizontalStrut(4)); + p.add(components[i]); + } + return p; + } + + private static Condition buildCondition(JComboBox typeCombo, JComboBox statCmpCombo, JSpinner statSpinner, + JTextField npcNameField, JComboBox npcPresentCombo, + JTextField objNameField, JComboBox objPresentCombo, + JTextField invNameField, JSpinner invCountSpinner, JComboBox invPresentCombo) + { + String sel = (String) typeCombo.getSelectedItem(); + if (sel == null || "None".equals(sel)) + { + return null; + } + Condition c = new Condition(); + switch (sel) + { + case "HP": + case "Prayer": + c.setType(ConditionType.STAT); + c.setStat("Prayer".equals(sel) ? StatKind.PRAYER : StatKind.HEALTH); + c.setComparator("above".equals(statCmpCombo.getSelectedItem()) ? ConditionComparator.ABOVE : ConditionComparator.BELOW); + c.setThreshold((Integer) statSpinner.getValue()); + return c; + case "NPC nearby": + { + String nm = npcNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.NPC_NEARBY); + c.setName(nm); + c.setPresent(!"absent".equals(npcPresentCombo.getSelectedItem())); + return c; + } + case "Object nearby": + { + String nm = objNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.OBJECT_NEARBY); + c.setName(nm); + c.setPresent(!"absent".equals(objPresentCombo.getSelectedItem())); + return c; + } + case "Inventory": + { + String nm = invNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.INVENTORY); + c.setName(nm); + c.setMinCount((Integer) invCountSpinner.getValue()); + c.setPresent(!"absent".equals(invPresentCombo.getSelectedItem())); + return c; + } + default: + return null; + } + } + + private void persistIfSaved(Recording r) + { + if (r == null || viewedRecording == null) + { + return; + } + try + { + plugin.save(r); + } + catch (IOException ex) + { + JOptionPane.showMessageDialog(this, + "Save failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + } + + private void onPlay() + { + Recording r = getSelectedRecording(); + if (r == null || r.size() == 0) + { + return; + } + plugin.play(r); + } + + private void onRename() + { + if (viewedRecording == null) + { + return; + } + String name = JOptionPane.showInputDialog(this, "New name:", viewedRecording.getName()); + if (name == null || name.trim().isEmpty()) + { + return; + } + try + { + plugin.rename(viewedRecording, name.trim()); + } + catch (IOException | IllegalArgumentException ex) + { + JOptionPane.showMessageDialog(this, + "Rename failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + refresh(); + } + + private void onDeleteScript() + { + if (viewedRecording == null) + { + return; + } + int choice = JOptionPane.showConfirmDialog(this, + "Delete recording '" + viewedRecording.getName() + "'?", + "AIO AIO", JOptionPane.OK_CANCEL_OPTION); + if (choice != JOptionPane.OK_OPTION) + { + return; + } + try + { + plugin.delete(viewedRecording); + } + catch (IOException ex) + { + JOptionPane.showMessageDialog(this, + "Delete failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + viewedRecording = null; + refresh(); + } + + private static String format(int idx, RecordedAction a) + { + Integer ticks = a.getDelayTicksBefore(); + String delay = ticks == null ? "—" : ticks + "t"; + String prefix = ""; + if (a.getCondition() != null) + { + String desc = a.getCondition().describe(); + if (!desc.isEmpty()) + { + prefix = "[" + desc + "] "; + } + } + return prefix + a.describe() + " (" + delay + ")"; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java new file mode 100644 index 0000000000..b3c5d60188 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java @@ -0,0 +1,526 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.Provides; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.MenuAction; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; +import net.runelite.client.RuneLite; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +@Slf4j +@PluginDescriptor( + name = PluginConstants.RED_BRACKET + "AIO AIO", + description = "Record and replay sequences of in-game actions. 80/20 automation for one-off tasks.", + tags = {"microbot", "aio", "record", "replay", "macro", "automation"}, + authors = { "Red Bracket" }, + version = ActionReplayPlugin.version, + minClientVersion = "2.1.32", + iconUrl = "https://chsami.github.io/Microbot-Hub/ActionReplayPlugin/assets/icon.png", + cardUrl = "https://chsami.github.io/Microbot-Hub/ActionReplayPlugin/assets/card.png", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +public class ActionReplayPlugin extends Plugin +{ + public static final String version = "1.1.0"; + + static final Path RECORDINGS_DIR = RuneLite.RUNELITE_DIR.toPath().resolve("microbot-recordings"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + @Inject + private ActionReplayConfig config; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ActionReplayOverlay overlay; + + @Inject + private ActionReplayScript script; + + @Getter + private volatile boolean recording = false; + @Getter + private volatile boolean playing = false; + + @Getter + private Recording currentRecording; + @Getter + private boolean appendingToExisting; + private int gameTickCounter; + private int lastActionTickCounter; + private MenuAction lastMenuAction; + private WorldPoint lastSeenDestination; + + private ActionReplayPanel panel; + private NavigationButton navButton; + + @Provides + ActionReplayConfig provideConfig(ConfigManager cm) + { + return cm.getConfig(ActionReplayConfig.class); + } + + @Override + protected void startUp() throws Exception + { + try + { + Files.createDirectories(RECORDINGS_DIR); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: could not create recordings dir " + RECORDINGS_DIR, e); + } + + panel = injector.getInstance(ActionReplayPanel.class); + panel.setPlugin(this); + panel.refresh(); + + navButton = NavigationButton.builder() + .tooltip("AIO AIO") + .icon(buildIcon()) + .priority(7) + .panel(panel) + .build(); + clientToolbar.addNavigation(navButton); + + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + stopRecording(false); + stopPlayback(); + overlayManager.remove(overlay); + if (navButton != null) + { + clientToolbar.removeNavigation(navButton); + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + if (!recording) + { + return; + } + gameTickCounter++; + WorldPoint dest = currentDestination(); + if ((lastMenuAction == MenuAction.WALK || lastMenuAction == MenuAction.CANCEL) + && dest != null + && !java.util.Objects.equals(dest, lastSeenDestination)) + { + lastMenuAction = null; + addWalkAction(dest); + } + lastSeenDestination = dest; + } + + private WorldPoint currentDestination() + { + LocalPoint local = Microbot.getClient().getLocalDestinationLocation(); + return local != null ? WorldPoint.fromLocalInstance(Microbot.getClient(), local) : null; + } + + private void addWalkAction(WorldPoint dest) + { + Recording rec = currentRecording; + if (rec == null) + { + return; + } + int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); + lastActionTickCounter = gameTickCounter; + RecordedAction a = new RecordedAction(); + a.setDelayTicksBefore(tickDelta); + a.setMenuOption("Walk to"); + a.setTargetName("(" + dest.getX() + ", " + dest.getY() + ", " + dest.getPlane() + ")"); + a.setMenuAction("WALK"); + a.setTargetType(TargetType.WALK); + a.setTargetX(dest.getX()); + a.setTargetY(dest.getY()); + a.setTargetPlane(dest.getPlane()); + rec.getActions().add(a); + if (panel != null) + { + panel.onActionRecorded(a); + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked e) + { + Recording rec = currentRecording; + if (!recording || rec == null || e.isConsumed()) + { + return; + } + MenuAction action = e.getMenuAction(); + lastMenuAction = action; + if (action == MenuAction.WALK || action == MenuAction.CANCEL) + { + return; + } + + int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); + lastActionTickCounter = gameTickCounter; + + RecordedAction a = new RecordedAction(); + a.setDelayTicksBefore(tickDelta); + a.setMenuOption(stripColorTags(e.getMenuOption())); + a.setTargetName(cleanTarget(e.getMenuTarget())); + a.setMenuAction(action == null ? null : action.name()); + a.setTargetType(TargetType.fromMenuAction(action)); + a.setIdentifier(e.getId()); + a.setParam0(e.getParam0()); + a.setParam1(e.getParam1()); + a.setItemId(e.getItemId()); + + Point mouse = Microbot.getClient().getMouseCanvasPosition(); + if (mouse != null) + { + a.setCanvasX(mouse.getX()); + a.setCanvasY(mouse.getY()); + } + + rec.getActions().add(a); + if (panel != null) + { + panel.onActionRecorded(a); + } + } + + public void startRecording(Recording existing) + { + if (recording) + { + return; + } + if (existing != null) + { + currentRecording = existing; + appendingToExisting = true; + log.info("ActionReplay: recording started (appending to '{}')", existing.getName()); + } + else + { + currentRecording = new Recording(); + currentRecording.setLastUsedAtEpochMs(System.currentTimeMillis()); + currentRecording.setName("Recording"); + appendingToExisting = false; + log.info("ActionReplay: recording started (new)"); + } + gameTickCounter = 0; + lastActionTickCounter = 0; + lastMenuAction = null; + lastSeenDestination = currentDestination(); + recording = true; + } + + public Recording stopRecording(boolean save) + { + if (!recording) + { + return null; + } + recording = false; + Recording r = currentRecording; + currentRecording = null; + if (r == null || r.size() == 0) + { + log.info("ActionReplay: recording stopped (empty, not saved)"); + if (panel != null) + { + panel.refresh(); + } + return null; + } + if (save) + { + if (!appendingToExisting) + { + enrichName(r); + r.setName(uniqueName(r.getName())); + } + try + { + save(r); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: failed to save recording " + r.getName(), e); + } + } + log.info("ActionReplay: recording stopped ({} actions)", r.size()); + if (panel != null) + { + panel.refresh(); + } + return r; + } + + public int getCurrentRecordingSize() + { + return currentRecording == null ? 0 : currentRecording.size(); + } + + public void play(Recording r) + { + if (playing) + { + return; + } + if (r == null || r.size() == 0) + { + log.warn("ActionReplay: recording is empty, nothing to play"); + return; + } + r.setLastUsedAtEpochMs(System.currentTimeMillis()); + Path savedFile = RECORDINGS_DIR.resolve(r.getName() + ".json"); + if (Files.exists(savedFile)) + { + try + { + save(r); + } + catch (IOException e) + { + log.warn("ActionReplay: failed to persist lastUsedAt for {}: {}", r.getName(), e.getMessage()); + } + } + playing = true; + if (panel != null) + { + panel.refresh(); + } + boolean started = script.play(r, () -> + { + playing = false; + if (panel != null) + { + panel.refresh(); + } + log.info("ActionReplay: playback finished"); + }); + if (!started) + { + playing = false; + if (panel != null) + { + panel.refresh(); + } + } + } + + public void stopPlayback() + { + if (!playing) + { + return; + } + script.shutdown(); + playing = false; + if (panel != null) + { + panel.refresh(); + } + } + + public List listRecordings() + { + List out = new ArrayList<>(); + if (!Files.isDirectory(RECORDINGS_DIR)) + { + return out; + } + try (Stream files = Files.list(RECORDINGS_DIR)) + { + files.filter(p -> p.getFileName().toString().endsWith(".json")) + .forEach(p -> + { + try + { + String json = new String(Files.readAllBytes(p)); + Recording r = GSON.fromJson(json, Recording.class); + if (r != null) + { + if (r.getName() == null) + { + String fn = p.getFileName().toString(); + r.setName(fn.substring(0, fn.length() - 5)); + } + if (r.getLastUsedAtEpochMs() == 0) + { + r.setLastUsedAtEpochMs(Files.getLastModifiedTime(p).toMillis()); + } + out.add(r); + } + } + catch (IOException ex) + { + log.warn("ActionReplay: failed to read {}: {}", p, ex.getMessage()); + } + }); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: failed to list recordings in " + RECORDINGS_DIR, e); + } + out.sort(Comparator.comparingLong(Recording::getLastUsedAtEpochMs).reversed()); + return out; + } + + public void save(Recording r) throws IOException + { + Files.createDirectories(RECORDINGS_DIR); + Path file = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Files.write(file, GSON.toJson(r).getBytes()); + log.info("ActionReplay: saved {}", file); + } + + public void rename(Recording r, String newName) throws IOException + { + String clean = sanitize(newName); + if (clean.isEmpty()) + { + throw new IllegalArgumentException("ActionReplay: recording name cannot be empty"); + } + Path oldFile = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Path newFile = RECORDINGS_DIR.resolve(clean + ".json"); + r.setName(clean); + Files.write(newFile, GSON.toJson(r).getBytes()); + if (!oldFile.equals(newFile) && Files.exists(oldFile)) + { + Files.delete(oldFile); + } + } + + public void delete(Recording r) throws IOException + { + Path file = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Files.deleteIfExists(file); + } + + private static String sanitize(String s) + { + if (s == null) + { + return ""; + } + return s.replaceAll("[^a-zA-Z0-9_.-]", "_").trim(); + } + + private void enrichName(Recording r) + { + if (r == null || r.size() == 0) + { + return; + } + RecordedAction first = r.getActions().get(0); + String verb = sanitize(first.getMenuOption()); + String target = sanitize(first.getTargetName()); + StringBuilder name = new StringBuilder(); + if (!verb.isEmpty()) + { + name.append(verb); + } + if (!target.isEmpty()) + { + if (name.length() > 0) + { + name.append("-"); + } + name.append(target); + } + if (name.length() > 0) + { + r.setName(name.toString()); + } + } + + private String uniqueName(String base) + { + if (base == null || base.isEmpty()) + { + base = "Recording"; + } + String candidate = base; + int n = 2; + while (Files.exists(RECORDINGS_DIR.resolve(candidate + ".json"))) + { + candidate = base + "-" + n; + n++; + } + return candidate; + } + + private static String stripColorTags(String s) + { + if (s == null) + { + return null; + } + return s.replaceAll("<[^>]+>", ""); + } + + private static String cleanTarget(String menuTarget) + { + String s = stripColorTags(menuTarget); + if (s == null) + { + return null; + } + return s.replaceAll("\\s*\\(level-\\d+\\)\\s*$", "").trim(); + } + + private static BufferedImage buildIcon() + { + BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics2D g = img.createGraphics(); + try + { + g.setColor(new java.awt.Color(220, 40, 40)); + g.fillOval(2, 2, 12, 12); + } + finally + { + g.dispose(); + } + return img; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java new file mode 100644 index 0000000000..b216b60d7b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java @@ -0,0 +1,291 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.MenuAction; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import java.awt.Rectangle; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +public class ActionReplayScript extends Script +{ + private static final int TARGET_LOOKUP_RADIUS = 20; + + private final AtomicBoolean abortFlag = new AtomicBoolean(false); + private Recording recording; + private Runnable onFinished; + + public boolean play(Recording recording, Runnable onFinished) + { + if (recording == null || recording.getActions() == null || recording.getActions().isEmpty()) + { + log.warn("ActionReplay: recording is empty, nothing to play"); + return false; + } + this.recording = recording; + this.onFinished = onFinished; + this.abortFlag.set(false); + + log.info("ActionReplay: starting playback of '{}' ({} actions)", recording.getName(), recording.size()); + + mainScheduledFuture = scheduledExecutorService.schedule(this::playbackLoop, 0, TimeUnit.MILLISECONDS); + return true; + } + + @Override + public void shutdown() + { + abortFlag.set(true); + super.shutdown(); + } + + private void playbackLoop() + { + try + { + while (!abortFlag.get()) + { + runOnce(); + } + } + catch (RuntimeException e) + { + if (e.getCause() instanceof InterruptedException || abortFlag.get()) + { + log.info("ActionReplay: playback stopped"); + } + else + { + log.error("ActionReplay playback failed", e); + } + } + finally + { + Runnable cb = onFinished; + onFinished = null; + if (cb != null) + { + cb.run(); + } + } + } + + private void runOnce() + { + for (int i = 0; i < recording.getActions().size(); i++) + { + if (abortFlag.get()) + { + return; + } + RecordedAction action = recording.getActions().get(i); + + Integer ticks = action.getDelayTicksBefore(); + if (ticks != null && ticks > 0) + { + sleep(ticks * 600); + } + + if (!Microbot.isLoggedIn()) + { + log.warn("ActionReplay: not logged in, aborting playback"); + return; + } + + if (action.getCondition() != null && !action.getCondition().check()) + { + log.info("ActionReplay: skipping step #{} ({}) — condition '{}' false", + i, action.describe(), action.getCondition().describe()); + continue; + } + + if (!executeStep(action)) + { + log.warn("ActionReplay: skipping step #{} ({})", i, action.describe()); + } + } + } + + private boolean executeStep(RecordedAction a) + { + TargetType type = a.getTargetType(); + if (type == null) + { + type = TargetType.UNKNOWN; + } + + String option = a.getMenuOption(); + log.debug("ActionReplay: step {} {} (type={})", option, a.getTargetName(), type); + + switch (type) + { + case NPC: + return replayNpc(a); + case GAME_OBJECT: + return replayGameObject(a); + case GROUND_ITEM: + return replayGroundItem(a); + case WALK: + return replayWalk(a); + case WIDGET: + case PLAYER: + case UNKNOWN: + default: + return replayRaw(a); + } + } + + private boolean replayNpc(RecordedAction a) + { + if (a.getTargetName() == null) + { + return false; + } +Rs2NpcModel match = Microbot.getRs2NpcCache().query() + .withName(a.getTargetName()) + .nearestOnClientThread(TARGET_LOOKUP_RADIUS); + if (match == null) + { + return false; + } + return Rs2Npc.interact(match.getId(), a.getMenuOption()); + } + + private boolean replayGameObject(RecordedAction a) + { + if (a.getTargetName() == null) + { + log.warn("ActionReplay: no target name for game object step, skipping '{}'", a.describe()); + return false; + } +Rs2TileObjectModel match = Microbot.getRs2TileObjectCache().query() + .withName(a.getTargetName()) + .nearestOnClientThread(TARGET_LOOKUP_RADIUS); + if (match == null) + { + log.warn("ActionReplay: no '{}' within {} tiles, skipping '{}'", + a.getTargetName(), TARGET_LOOKUP_RADIUS, a.describe()); + return false; + } + return match.click(a.getMenuOption()); + } + + private boolean replayWalk(RecordedAction a) + { + WorldPoint dest = new WorldPoint(a.getTargetX(), a.getTargetY(), a.getTargetPlane()); + return Rs2Walker.walkTo(dest); + } + + private boolean replayGroundItem(RecordedAction a) + { + if (a.getTargetName() == null) + { + return false; + } + return Rs2GroundItem.loot(a.getTargetName(), TARGET_LOOKUP_RADIUS); + } + + private boolean replayRaw(RecordedAction a) + { + MenuAction ma = parseMenuAction(a.getMenuAction()); + if (ma == null) + { + log.warn("ActionReplay: unknown MenuAction '{}', cannot replay raw step", a.getMenuAction()); + return false; + } + + int param1 = a.getParam1(); + + if ((param1 >>> 16) > 0 && !Rs2Widget.isWidgetVisible(param1)) + { + log.warn("ActionReplay: widget {} not visible, skipping '{}'", param1, a.describe()); + return false; + } + + if (a.getItemId() > 0 && param1 > 0 && (param1 >>> 16) == InterfaceID.INVENTORY + && a.getMenuOption() != null && !a.getMenuOption().isEmpty()) + { + String itemName = a.getTargetName(); + if (itemName == null || itemName.isEmpty()) + { + log.warn("ActionReplay: no item name for inventory step, skipping '{}'", a.describe()); + return false; + } + if (!Rs2Inventory.hasItem(itemName, true)) + { + log.warn("ActionReplay: item '{}' not in inventory, skipping '{}'", itemName, a.describe()); + return false; + } + return Rs2Inventory.interact(itemName, a.getMenuOption(), true); + } + + NewMenuEntry entry = new NewMenuEntry( + a.getMenuOption(), + a.getTargetName() != null ? a.getTargetName() : "", + a.getIdentifier(), + ma, + a.getParam0(), + param1, + false + ); + entry.setItemId(a.getItemId()); + try + { + Rectangle rect = buildClickRect(a); + Microbot.doInvoke(entry, rect); + return true; + } + catch (Exception e) + { + log.warn("ActionReplay: doInvoke failed for step {}: {}", a.describe(), e.getMessage()); + return false; + } + } + + private Rectangle buildClickRect(RecordedAction a) + { + if (a.getCanvasX() > 0 && a.getCanvasY() > 0) + { + return new Rectangle(a.getCanvasX() - 2, a.getCanvasY() - 2, 4, 4); + } + return new Rectangle(1, 1); + } + + private MenuAction parseMenuAction(String s) + { + if (s == null || s.isEmpty()) + { + return null; + } + try + { + return MenuAction.valueOf(s); + } + catch (IllegalArgumentException ex) + { + return null; + } + } + + public boolean isPlaying() + { + return mainScheduledFuture != null && !mainScheduledFuture.isDone() && !abortFlag.get(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java new file mode 100644 index 0000000000..dc6c826628 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java @@ -0,0 +1,128 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Data +public class Condition +{ + public static final int NEARBY_RADIUS = 20; + + private ConditionType type; + private StatKind stat; + private ConditionComparator comparator; + private Integer threshold; + private String name; + private Boolean present; + private Integer minCount; + + public boolean check() + { + if (type == null) + { + return true; + } + switch (type) + { + case STAT: + return checkStat(); + case NPC_NEARBY: + return checkNpcNearby(); + case OBJECT_NEARBY: + return checkObjectNearby(); + case INVENTORY: + return checkInventory(); + default: + return true; + } + } + + private boolean checkStat() + { + if (stat == null || comparator == null || threshold == null) + { + return true; + } + Skill skill = stat == StatKind.HEALTH ? Skill.HITPOINTS : Skill.PRAYER; + int current = Rs2Player.getBoostedSkillLevel(skill); + return comparator == ConditionComparator.ABOVE ? current > threshold : current < threshold; + } + + private boolean checkNpcNearby() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + boolean found = Microbot.getRs2NpcCache().query() + .withName(name) + .nearestOnClientThread(NEARBY_RADIUS) != null; + return found == present; + } + + private boolean checkObjectNearby() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + boolean found = Microbot.getRs2TileObjectCache().query() + .withName(name) + .nearestOnClientThread(NEARBY_RADIUS) != null; + return found == present; + } + + private boolean checkInventory() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + int need = minCount == null ? 1 : Math.max(1, minCount); + int have = Rs2Inventory.count(name, true); + boolean meets = have >= need; + return meets == present; + } + + public String describe() + { + if (type == null) + { + return ""; + } + switch (type) + { + case STAT: + if (stat == null || comparator == null || threshold == null) + { + return ""; + } + String sn = stat == StatKind.HEALTH ? "HP" : "Prayer"; + String op = comparator == ConditionComparator.ABOVE ? ">" : "<"; + return "if " + sn + op + threshold; + case NPC_NEARBY: + case OBJECT_NEARBY: + if (name == null || present == null) + { + return ""; + } + return "if " + (present ? "" : "no ") + name + " nearby"; + case INVENTORY: + if (name == null || present == null) + { + return ""; + } + int need = minCount == null ? 1 : Math.max(1, minCount); + if (present) + { + return "if inv has " + (need > 1 ? need + "x " : "") + name; + } + return "if inv lacks " + name; + default: + return ""; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java new file mode 100644 index 0000000000..8b6c7a14d8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum ConditionComparator +{ + ABOVE, + BELOW +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java new file mode 100644 index 0000000000..52380b9b75 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum ConditionType +{ + STAT, + NPC_NEARBY, + OBJECT_NEARBY, + INVENTORY +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java new file mode 100644 index 0000000000..eb642aedd3 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java @@ -0,0 +1,32 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; + +@Data +public class RecordedAction +{ + private Integer delayTicksBefore; + private String menuOption; + private String menuAction; + private TargetType targetType; + private int identifier; + private int param0; + private int param1; + private int itemId; + private String targetName; + private int canvasX; + private int canvasY; + private int targetX; + private int targetY; + private int targetPlane; + private Condition condition; + + public String describe() + { + if (targetName == null || targetName.isEmpty()) + { + return menuOption; + } + return menuOption + " → " + targetName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java new file mode 100644 index 0000000000..aa3ad132e2 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Recording +{ + private String name; + private long lastUsedAtEpochMs; + private List actions = new ArrayList<>(); + + public int size() + { + return actions == null ? 0 : actions.size(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java new file mode 100644 index 0000000000..ec025d8c78 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum StatKind +{ + HEALTH, + PRAYER +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java new file mode 100644 index 0000000000..56b3d3a1cc --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java @@ -0,0 +1,71 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import net.runelite.api.MenuAction; + +public enum TargetType +{ + NPC, + GAME_OBJECT, + GROUND_ITEM, + PLAYER, + WIDGET, + WALK, + UNKNOWN; + + public static TargetType fromMenuAction(MenuAction action) + { + if (action == null) + { + return UNKNOWN; + } + switch (action) + { + case NPC_FIRST_OPTION: + case NPC_SECOND_OPTION: + case NPC_THIRD_OPTION: + case NPC_FOURTH_OPTION: + case NPC_FIFTH_OPTION: + case EXAMINE_NPC: + return NPC; + case GAME_OBJECT_FIRST_OPTION: + case GAME_OBJECT_SECOND_OPTION: + case GAME_OBJECT_THIRD_OPTION: + case GAME_OBJECT_FOURTH_OPTION: + case GAME_OBJECT_FIFTH_OPTION: + case EXAMINE_OBJECT: + return GAME_OBJECT; + case GROUND_ITEM_FIRST_OPTION: + case GROUND_ITEM_SECOND_OPTION: + case GROUND_ITEM_THIRD_OPTION: + case GROUND_ITEM_FOURTH_OPTION: + case GROUND_ITEM_FIFTH_OPTION: + case EXAMINE_ITEM_GROUND: + return GROUND_ITEM; + case PLAYER_FIRST_OPTION: + case PLAYER_SECOND_OPTION: + case PLAYER_THIRD_OPTION: + case PLAYER_FOURTH_OPTION: + case PLAYER_FIFTH_OPTION: + case PLAYER_SIXTH_OPTION: + case PLAYER_SEVENTH_OPTION: + case PLAYER_EIGHTH_OPTION: + return PLAYER; + case WALK: + return WALK; + case CC_OP: + case CC_OP_LOW_PRIORITY: + case WIDGET_TYPE_1: + case WIDGET_TYPE_4: + case WIDGET_TYPE_5: + case WIDGET_TARGET: + case WIDGET_TARGET_ON_GAME_OBJECT: + case WIDGET_TARGET_ON_GROUND_ITEM: + case WIDGET_TARGET_ON_NPC: + case WIDGET_TARGET_ON_PLAYER: + case WIDGET_TARGET_ON_WIDGET: + return WIDGET; + default: + return UNKNOWN; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java index a5d2cea7a8..d764c1038b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java @@ -14,10 +14,11 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import java.util.List; import java.util.concurrent.TimeUnit; import static net.runelite.client.plugins.microbot.util.npc.Rs2Npc.validateInteractable; @@ -62,10 +63,10 @@ public boolean run(AerialFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if (Rs2Npc.interact(fishingspot)) { + if (fishingspot.click()) { if (sleepUntil(Rs2Player::isInteracting, 1200)) { sleepUntil(() -> Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD), () -> { if ((Rs2Inventory.emptySlotCount() <= 1 && Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_NO_BIRD)) || (Rs2Inventory.emptySlotCount() == 0 && Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD))) { @@ -74,9 +75,9 @@ public boolean run(AerialFishingConfig config) { Rs2Inventory.hover(knife); } else { - NPC preHoverSpot = findPreHoverSpot(fishingspot); + Rs2NpcModel preHoverSpot = findPreHoverSpot(fishingspot); if (preHoverSpot != null) { - if (Rs2Npc.hoverOverActor(preHoverSpot)) { + if (Rs2Npc.hoverOverActor(preHoverSpot.getNpc())) { if (Rs2Random.dicePercentage(20)) { Microbot.getMouse().click(); @@ -96,11 +97,12 @@ public boolean run(AerialFishingConfig config) { private Rs2NpcModel findFishingSpot() { - return Rs2Npc.getNpc(NpcID.FISHING_SPOT_AERIAL); + return Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_AERIAL).nearest(); } - private NPC findPreHoverSpot(NPC exludedSpot) { - return Rs2Npc.getNpcs(NpcID.FISHING_SPOT_AERIAL).filter(x -> x != exludedSpot).findFirst().orElse(null); + private Rs2NpcModel findPreHoverSpot(Rs2NpcModel excludedSpot) { + List spots = Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_AERIAL).toList(); + return spots.stream().filter(x -> x != excludedSpot).findFirst().orElse(null); } private void cutFish() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java index a04be2d89c..1aca10a22f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class MicroAgilityPlugin extends Plugin { - public static final String version = "1.2.5"; + public static final String version = "1.2.6"; @Inject private MicroAgilityConfig config; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java index 0279f6a478..854fe51c57 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java @@ -4,9 +4,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.agility.models.AgilityObstacleModel; import net.runelite.client.plugins.microbot.util.Global; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -87,14 +85,13 @@ public boolean handlePayment() { } // Find and interact with Cap'n Izzy No-Beard - var captain = Rs2Npc.getNpc(CAPTAIN_IZZY_NPC_ID); + var captain = Microbot.getRs2NpcCache().query().withId(CAPTAIN_IZZY_NPC_ID).nearest(); if (captain == null) { Microbot.log("Cap'n Izzy No-Beard not found!"); return false; } - // Click "Pay" option - if (Rs2Npc.interact(captain, "Pay")) { + if (captain.click("Pay")) { Microbot.log("Attempting to pay Cap'n Izzy No-Beard..."); // Wait for coins to be deducted @@ -127,7 +124,7 @@ public boolean handleLadderDescent() { // Just click the ladder directly without detection Microbot.log("Force clicking ladder..."); - Rs2GameObject.interact(3617, "Climb-down"); + Microbot.getRs2TileObjectCache().query().interact(3617, "Climb-down"); // Wait a bit for the interaction Global.sleep(1000); @@ -281,7 +278,7 @@ public net.runelite.api.TileObject getCurrentObstacle() { return null; // This will trigger the timed tile-walking logic in handleWalkToStart } - var gameObject = Rs2GameObject.getGameObject(currentObstacle.getObjectID(), playerLocation, 10); + var gameObject = Microbot.getRs2TileObjectCache().query().withId(currentObstacle.getObjectID()).within(playerLocation, 10).nearest(); if (gameObject != null) { Microbot.log("Looking for obstacle " + (currentObstacleIndex + 1) + "/" + obstacles.size() + " (ID: " + currentObstacle.getObjectID() + ")"); return gameObject; diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java index 1cf5b8f800..79c6e72c57 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java @@ -65,10 +65,10 @@ public Integer getRequiredLevel() public boolean handlePortal() { - TileObject portal = Rs2GameObject.getGameObject(PORTAL_OBSTACLE_IDS.toArray(new Integer[0]), 10); - if (portal != null && Microbot.getClientThread().runOnClientThreadOptional(portal::getClickbox).isPresent()) + var portalModel = Microbot.getRs2TileObjectCache().query().withIds(PORTAL_OBSTACLE_IDS.stream().mapToInt(Integer::intValue).toArray()).within(10).nearest(); + if (portalModel != null && Microbot.getClientThread().runOnClientThreadOptional(portalModel::getClickbox).isPresent()) { - if (Rs2GameObject.interact(portal, "travel")) + if (portalModel.click("travel")) { Global.sleep(2000, 3000); return true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java index 0728dcbaa3..e33f484cba 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java @@ -11,8 +11,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -1126,7 +1125,7 @@ private boolean handlePyramidTurnIn() { } // Try to find Simon - Rs2NpcModel simon = Rs2Npc.getNpc(SIMON_NAME); + Rs2NpcModel simon = Microbot.getRs2NpcCache().query().withName(SIMON_NAME).nearestOnClientThread(); // If Simon is found and reachable, use pyramid top on him if (simon != null && Rs2GameObject.canReach(simon.getWorldLocation())) { @@ -1149,7 +1148,7 @@ private boolean handlePyramidTurnIn() { } } else { // Not in dialogue, use pyramid top on Simon - boolean used = Rs2Inventory.useItemOnNpc(ItemID.AGILITY_PYRAMID_GOLD_PYRAMID, simon); + boolean used = Rs2Inventory.useItemOnNpc(ItemID.AGILITY_PYRAMID_GOLD_PYRAMID, simon.getNpc()); if (used) { log.debug("Successfully used pyramid top on Simon"); Global.sleepUntil(() -> Rs2Dialogue.isInDialogue(), 3000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java index 08c98c379c..ddc02b8810 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java @@ -16,7 +16,6 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Operation; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import org.slf4j.event.Level; @@ -119,9 +118,9 @@ public boolean handleFirstSteppingStone(WorldPoint playerWorldLocation) { else { // Login edge case where we end up in not defined walker area? if(Rs2Walker.walkTo(RESET_WORLD_POINT, 5)) // Try one more time return true; - var agilityBoss = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_START); // Try clicking on NPC to move to right area? + var agilityBoss = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_START).nearest(); if(agilityBoss != null) { - Rs2Npc.interact(agilityBoss); + agilityBoss.click(); return true; } } @@ -182,15 +181,15 @@ public boolean handleStickReturn(WorldPoint playerWorldLocation) { private static void returnStick(WorldPoint playerWorldLocation) { if (Rs2Inventory.hasItem("Stick")) { - var stickNpc = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_STICK); + var stickNpc = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_STICK).nearest(); if(stickNpc == null) { Rs2Walker.walkTo(STICK_NPC_WORLD_POINT, 5); - stickNpc = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_STICK); + stickNpc = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_STICK).nearest(); } if (stickNpc != null) { if (playerWorldLocation.distanceTo(stickNpc.getWorldLocation()) > 5) Rs2Walker.walkTo(stickNpc.getWorldLocation(), 5); - Rs2Npc.interact(stickNpc, "Give-Stick"); + stickNpc.click("Give-Stick"); Rs2Player.waitForWalking(); } else { Microbot.log("Could not find stick NPC!", Level.WARN); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java index 0bc704889d..8d7923941d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java @@ -2,7 +2,7 @@ import net.runelite.api.Point; import net.runelite.api.Skill; -import net.runelite.api.WallObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.AnimationID; @@ -16,15 +16,13 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -53,7 +51,7 @@ enum State { State state = State.WALKING_TO_BANK; - List mineableRocks = new ArrayList<>(); + List mineableRocks = new ArrayList<>(); int pickaxeToUse; int netToUse; @@ -191,17 +189,17 @@ private void MineAndSmith() { if (lastInteractedBarroniteID == 41547) { //System.out.println(("Last Interacted Rock: LEFT Barronite Vein")); for (int miningObjectID : miningObjectIDsReverse) { - mineableRocks.addAll(Rs2GameObject.getWallObjects(object -> object != null && (object.getId() == 41548))); + mineableRocks.addAll(Microbot.getRs2TileObjectCache().query().withId(41548).toList()); } } else if (lastInteractedBarroniteID == 41548) { //System.out.println(("Last Interacted Rock: RIGHT Barronite Vein")); for (int miningObjectID : miningObjectIDs) { - mineableRocks.addAll(Rs2GameObject.getWallObjects(object -> object != null && (object.getId() == 41547))); + mineableRocks.addAll(Microbot.getRs2TileObjectCache().query().withId(41547).toList()); } } if (!mineableRocks.isEmpty()) { - Rs2GameObject.interact(mineableRocks.get(0), "Mine"); + mineableRocks.get(0).click("Mine"); lastInteractedBarroniteID = mineableRocks.get(0).getId(); Microbot.status = "Mining Barronite rocks"; } @@ -231,7 +229,7 @@ private void MineAndSmith() { if (Rs2Player.getAnimation() != smithingAnimationID) { if (Rs2Inventory.hasItem(ItemID.CAMDOZAAL_BARRONITE_DEPOSIT)) { if (!sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.SMITHING), 3000)) { - Rs2GameObject.interact(crusherID, "Smith"); + Microbot.getRs2TileObjectCache().query().interact(crusherID, "Smith"); Microbot.status = "Smithing Barronite deposits"; } } else { @@ -373,9 +371,9 @@ private void FishAndCook() { } if (!Rs2Player.isInteracting() && !Rs2Player.isMoving()) { if (netToUse == 303) { - Rs2Npc.interact(10686, "Small Net"); + Microbot.getRs2NpcCache().query().withId(10686).interact("Small Net"); } else if (netToUse == 305) { - Rs2Npc.interact(10686, "Big Net"); + Microbot.getRs2NpcCache().query().withId(10686).interact("Big Net"); } } @@ -394,7 +392,7 @@ private void FishAndCook() { Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); Rs2Player.waitForXpDrop(Skill.COOKING, 3000); } else if (!Rs2Player.isMoving()) { - Rs2GameObject.interact(41545, "Prepare-fish"); + Microbot.getRs2TileObjectCache().query().interact(41545, "Prepare-fish"); } } else { @@ -412,7 +410,7 @@ private void FishAndCook() { Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); Rs2Player.waitForXpDrop(Skill.PRAYER, 3000); } else if (!Rs2Player.isMoving()) { - Rs2GameObject.interact(41546, "Offer-fish"); + Microbot.getRs2TileObjectCache().query().interact(41546, "Offer-fish"); } } else { if (Rs2Inventory.hasItem(ItemID.BARRONITE_MACE_2)) { @@ -553,15 +551,15 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, state = State.WALKING_TO_BANK; } - golemsAttackingPlayer = Rs2Npc.getNpcsForPlayer().collect(Collectors.toList()); + golemsAttackingPlayer = Microbot.getRs2NpcCache().query().where(n -> n.isInteractingWithPlayer()).toList(); if (!Rs2Combat.inCombat()) { // Not in combat if (!golemsAttackingPlayer.isEmpty()) { // Filter by things with an Attack option Rs2NpcModel targetGolem = golemsAttackingPlayer.stream() - .filter(npc -> npc.getComposition() != null && - Arrays.stream(npc.getComposition().getActions()) + .filter(npc -> npc.getNpc().getTransformedComposition() != null && + Arrays.stream(npc.getNpc().getTransformedComposition().getActions()) .anyMatch(action -> action != null && action.toLowerCase().contains("attack"))) .findFirst().orElse(null); @@ -570,14 +568,14 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(targetGolem); } - Rs2Npc.interact(targetGolem, "Attack"); + targetGolem.click("Attack"); Rs2Antiban.actionCooldown(); } break; } - golems = Rs2Npc.getNpcs(_golemID).collect(Collectors.toList()); - rubbles = Rs2Npc.getNpcs(_rubbleID).collect(Collectors.toList()); + golems = Microbot.getRs2NpcCache().query().withId(_golemID).toList(); + rubbles = Microbot.getRs2NpcCache().query().withId(_rubbleID).toList(); if (!golems.isEmpty()) { Rs2NpcModel golem = golems.stream().findFirst().orElse(null); @@ -586,7 +584,7 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(golem); } - Rs2Npc.interact(golem, "Attack"); + golem.click("Attack"); Rs2Antiban.actionCooldown(); } else { if (!rubbles.isEmpty()) { @@ -596,7 +594,7 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(rubble); } - Rs2Npc.interact(rubble, "Awaken"); + rubble.click("Awaken"); Rs2Antiban.actionCooldown(); sleepUntil(() -> !golems.isEmpty(), 2000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java index 5b11b15b63..ba84deb70d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java @@ -915,7 +915,7 @@ default int slayerTaskWeaknessThreshold() { hidden = true ) default String blacklistedSlayerNpcs() { - return "null,"; + return ""; } @@ -964,4 +964,3 @@ default WorldPoint safeSpot() { } } - diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java index 853e2fb5f6..e3237368d9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java @@ -9,8 +9,8 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.aiofighter.combat.AttackNpcScript; import net.runelite.client.plugins.microbot.aiofighter.model.Monster; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index c7adc05c78..c885c964d2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -38,6 +38,7 @@ import javax.inject.Inject; import java.awt.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -61,7 +62,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.0.13"; + public static final String version = "2.1.7"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -263,13 +264,30 @@ public static InventorySetup getDefaultInventorySetup() { } public static void addBlacklistedSlayerNpcs(String npcName) { - String existing = getConfig("blacklistedSlayerNpcs", String.class); - setConfig("blacklistedSlayerNpcs", existing + npcName + ","); + if (npcName == null || npcName.trim().isEmpty()) { + return; + } + + LinkedHashSet blacklistedNpcs = getBlacklistedSlayerNpcSet(); + blacklistedNpcs.add(npcName.trim()); + setConfig("blacklistedSlayerNpcs", String.join(",", blacklistedNpcs) + ","); } public static List getBlacklistedSlayerNpcs() { + return new ArrayList<>(getBlacklistedSlayerNpcSet()); + } + + private static LinkedHashSet getBlacklistedSlayerNpcSet() { String stored = getConfig("blacklistedSlayerNpcs", String.class); - return Arrays.asList(stored.split(",")); + if (stored == null || stored.trim().isEmpty()) { + return new LinkedHashSet<>(); + } + + return Arrays.stream(stored.split(",")) + .map(String::trim) + .filter(entry -> !entry.isEmpty()) + .filter(entry -> !"null".equalsIgnoreCase(entry)) + .collect(Collectors.toCollection(LinkedHashSet::new)); } private static LinkedHashSet normalizeCsvEntries(String rawCsv) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index e6c9e516fe..5eeb64cf96 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.aiofighter.bank; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.ItemComposition; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; @@ -14,13 +13,13 @@ import net.runelite.client.plugins.microbot.aiofighter.shop.ShopItem; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopScript; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopType; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.inventorysetups.MInventorySetupsPlugin; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -58,12 +57,14 @@ public boolean run(AIOFighterConfig config) { try { if (!Microbot.isLoggedIn()) return; if(!super.run()) return; + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) return; if (config.bank() && needBanking() && !AIOFighterPlugin.needShopping) { if(handleBanking()){ Microbot.log("Banking handled successfully."); } } else if (!needBanking() && - config.centerLocation().distanceTo(Rs2Player.getWorldLocation()) > config.attackRadius() && + config.centerLocation().distanceTo(playerLocation) > config.attackRadius() && !config.centerLocation().equals(new WorldPoint(0, 0, 0))) { boolean shouldWalk = false; @@ -474,10 +475,15 @@ private void usePoolIfNeeded() { sleepUntil(() -> !Rs2Bank.isOpen(), 2000); } - // Find and use the pool - GameObject pool = Rs2GameObject.get("Pool of Refreshment", true); + // Find and use the pool. Bound to 20 tiles — replaces the legacy + // Rs2GameObject.interact 51-tile walk-to fallback. The new model's click() + // does not walk-to-object, so we must guard distance up front to avoid + // silently dispatching a click against an unreachable pool. + Rs2TileObjectModel pool = Microbot.getRs2TileObjectCache().query() + .withName("Pool of Refreshment") + .nearestOnClientThread(20); if (pool != null) { - if (Rs2GameObject.interact(pool, "Drink")) { + if (pool.click("Drink")) { sleepUntil(Rs2Player::isMoving, 2000); sleepUntil(() -> !Rs2Player.isMoving(), 5000); sleepUntil(Rs2Player::isAnimating, 2000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index c56a133b22..991fbf8336 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -2,6 +2,10 @@ import lombok.SneakyThrows; import net.runelite.api.Actor; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.WorldView; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -11,21 +15,20 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.State; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; -import net.runelite.client.plugins.microbot.util.ActorModel; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; +import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2Cannon; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.item.Rs2EnsouledHead; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -37,6 +40,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -88,8 +93,11 @@ public void run(AIOFighterConfig config) { if (!config.toggleCombat()) return; - if (config.centerLocation().distanceTo(Rs2Player.getWorldLocation()) < config.attackRadius() && - !config.centerLocation().equals(new WorldPoint(0, 0, 0)) && AIOFighterPlugin.getState() != State.BANKING) { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation != null + && config.centerLocation().distanceTo(playerLocation) < config.attackRadius() + && !config.centerLocation().equals(new WorldPoint(0, 0, 0)) + && AIOFighterPlugin.getState() != State.BANKING) { if (ShortestPathPlugin.getPathfinder() != null) Rs2Walker.setTarget(null); AIOFighterPlugin.setState(State.IDLE); @@ -104,15 +112,15 @@ public void run(AIOFighterConfig config) { boolean prevPause = Microbot.pauseAllScripts.getAndSet(true); try { if (head.reanimate()) { - sleepUntil(() -> Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated).findAny().isPresent(), 15000); + sleepUntil(() -> findReanimatedHeadOnPlayer() != null, 15000); } } finally { Microbot.pauseAllScripts.set(prevPause); } } - Rs2NpcModel reanimated = Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated).findAny().orElse(null); + Rs2NpcModel reanimated = findReanimatedHeadOnPlayer(); if (reanimated != null) { - Rs2Npc.interact(reanimated, "Attack"); + reanimated.click("Attack"); return; } } @@ -120,27 +128,42 @@ public void run(AIOFighterConfig config) { attackableArea = new Rs2WorldArea(config.centerLocation().toWorldArea()); attackableArea = attackableArea.offset(config.attackRadius()); - List npcsToAttack = Arrays.stream(config.attackableNpcs().split(",")) + final Set npcsToAttack = Arrays.stream(config.attackableNpcs().split(",")) .map(x -> x.trim().toLowerCase()) + .filter(x -> !x.isEmpty()) + .collect(Collectors.toSet()); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + final WorldPoint centerLocation = config.centerLocation(); + final int attackRadius = config.attackRadius(); + final boolean requireReachable = config.attackReachableNpcs(); + final Rs2WorldPoint rs2PlayerPoint = Rs2Player.getRs2WorldPoint(); + // Inside an instance, npc.getWorldLocation() returns the instance-side coord + // (high-corner template region), but centerLocation/centre-tile and + // Rs2Player.getWorldLocation() are template/overworld coords. Convert via the + // npc's scene-local point so both sides of the radius/path checks match. + final WorldView worldView = Microbot.getClient().getTopLevelWorldView(); + final boolean instanced = worldView != null && worldView.getScene().isInstance(); + + List attackableNpcs = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getCombatLevel() > 0 && !npc.isDead()) + .where(npc -> !npc.isInteracting() || Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> { + WorldPoint loc = npcWorldLocation(npc, instanced); + if (loc == null) return false; + if (loc.distanceTo(centerLocation) > attackRadius) return false; + return !requireReachable || rs2PlayerPoint.distanceToPath(loc) < Integer.MAX_VALUE; + }) + .where(npc -> { + String name = npc.getName(); + return name != null && !npcsToAttack.isEmpty() && npcsToAttack.contains(name.toLowerCase()); + }) + .toList() + .stream() + .sorted(Comparator + .comparingInt((Rs2NpcModel npc) -> Objects.equals(npc.getInteracting(), localPlayer) ? 0 : 1) + .thenComparingInt(npc -> rs2PlayerPoint.distanceToPath(npcWorldLocation(npc, instanced)))) .collect(Collectors.toList()); - filteredAttackableNpcs.set( - Rs2Npc.getAttackableNpcs(config.attackReachableNpcs()) - .filter(npc -> npc.getWorldLocation().distanceTo(config.centerLocation()) <= config.attackRadius()) - .filter(npc -> npc.getName() != null && !npcsToAttack.isEmpty() && npcsToAttack.stream().anyMatch(npc.getName()::equalsIgnoreCase)) - .sorted(Comparator.comparingInt((Rs2NpcModel npc) -> npc.getInteracting() == Microbot.getClient().getLocalPlayer() ? 0 : 1) - .thenComparingInt(npc -> Rs2Player.getRs2WorldPoint().distanceToPath(npc.getWorldLocation()))) - .collect(Collectors.toList()) - ); - final List attackableNpcs = new ArrayList<>(); - - for (var attackableNpc : filteredAttackableNpcs.get()) { - if (attackableNpc == null || attackableNpc.getName() == null) continue; - for (var npcToAttack : npcsToAttack) { - if (npcToAttack.equalsIgnoreCase(attackableNpc.getName())) { - attackableNpcs.add(attackableNpc); - } - } - } + filteredAttackableNpcs.set(attackableNpcs); // Check if we should pause while looting is happening @@ -151,19 +174,21 @@ public void run(AIOFighterConfig config) { // Check if we need to update our cached target (but not while waiting for loot) if (!AIOFighterPlugin.isWaitingForLoot()) { Actor currentInteracting = Rs2Player.getInteracting(); - if (currentInteracting instanceof Rs2NpcModel) { - Rs2NpcModel npc = (Rs2NpcModel) currentInteracting; + if (currentInteracting instanceof NPC) { + NPC interactingNpc = (NPC) currentInteracting; // Update our cached target to who we're fighting - if (npc.getHealthRatio() > 0 && !npc.isDead()) { - cachedTargetNpcIndex = npc.getIndex(); + if (interactingNpc.getHealthRatio() > 0 && !interactingNpc.isDead()) { + cachedTargetNpcIndex = interactingNpc.getIndex(); } } } // Check if our cached target died if (config.toggleWaitForLoot() && !AIOFighterPlugin.isWaitingForLoot() && cachedTargetNpcIndex != -1) { - // Find the NPC by index using Rs2 API - Rs2NpcModel cachedNpcModel = Rs2Npc.getNpcByIndex(cachedTargetNpcIndex); + final int targetIndex = cachedTargetNpcIndex; + Rs2NpcModel cachedNpcModel = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getIndex() == targetIndex) + .first(); if (cachedNpcModel != null && (cachedNpcModel.isDead() || (cachedNpcModel.getHealthRatio() == 0 && cachedNpcModel.getHealthScale() > 0))) { AIOFighterPlugin.setWaitingForLoot(true); @@ -218,12 +243,12 @@ public void run(AIOFighterConfig config) { if (!attackableNpcs.isEmpty()) { noNpcCount = 0; - Rs2NpcModel npc = attackableNpcs.stream().findFirst().orElse(null); + Rs2NpcModel npc = attackableNpcs.get(0); if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) Rs2Camera.turnTo(npc); - Rs2Npc.interact(npc, "attack"); + npc.click("attack"); Microbot.status = "Attacking " + npc.getName(); Rs2Antiban.actionCooldown(); //sleepUntil(Rs2Player::isInteracting, 1000); @@ -278,21 +303,60 @@ public void run(AIOFighterConfig config) { * item on npcs that need to kill like rockslug */ private void handleItemOnNpcToKill(AIOFighterConfig config) { - Rs2NpcModel npc = Rs2Npc.getNpcsForPlayer(ActorModel::isDead).findFirst().orElse(null); - List lizardVariants = new ArrayList<>(Arrays.asList("Lizard", "Desert Lizard", "Small Lizard")); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> n.isDead() && Objects.equals(n.getInteracting(), localPlayer)) + .first(); if (npc == null) return; + List lizardVariants = new ArrayList<>(Arrays.asList("Lizard", "Desert Lizard", "Small Lizard")); + // Rs2Inventory.useItemOnNpc only accepts the legacy util.npc.Rs2NpcModel — wrap our underlying NPC at the call site. + net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel legacyNpc = + new net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel(npc.getNpc()); if (Microbot.getVarbitValue(SLAYER_AUTOKILL_DESERTLIZARDS) == 0 && lizardVariants.contains(npc.getName()) && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, legacyNpc); Rs2Player.waitForAnimation(); } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_ROCKSLUGS) == 0 && npc.getName().equalsIgnoreCase("rockslug") && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, legacyNpc); Rs2Player.waitForAnimation(); } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_GARGOYLES) == 0 && npc.getName().equalsIgnoreCase("gargoyle") && npc.getHealthRatio() < 3) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, legacyNpc); Rs2Player.waitForAnimation(); } } + /** + * Finds a reanimated head NPC the player is currently interacting with. + * Replaces the legacy {@code Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated)} call. + */ + private Rs2NpcModel findReanimatedHeadOnPlayer() { + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + if (localPlayer == null) return null; + return Microbot.getRs2NpcCache().query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> { + String name = npc.getName(); + return name != null && name.contains("Reanimated"); + }) + .first(); + } + + /** + * Returns the npc's world location in the same coordinate space as + * {@code config.centerLocation()} / {@code Rs2Player.getWorldLocation()}. + * Inside an instance the raw {@code npc.getWorldLocation()} reports the + * instance-side coord (high-corner template region) while the centre tile + * is stored as a template/overworld coord, so distances would never match. + */ + private static WorldPoint npcWorldLocation(Rs2NpcModel npc, boolean instanced) { + if (instanced) { + LocalPoint lp = npc.getLocalLocation(); + if (lp != null) { + return WorldPoint.fromLocalInstance(Microbot.getClient(), lp); + } + } + return npc.getWorldLocation(); + } + @Override public void shutdown() { super.shutdown(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java index f468f0e250..75184478e5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Player; import net.runelite.api.events.NpcDespawned; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -9,10 +10,9 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; import net.runelite.client.plugins.microbot.aiofighter.model.Monster; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -69,8 +69,19 @@ public boolean run(AIOFighterConfig config) { break; } - // Atomically update NPC snapshot - npcsRef.set(Rs2Npc.getNpcsForPlayer().collect(Collectors.toList())); + // Atomically update NPC snapshot — all NPCs currently interacting with the local player. + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + // Logout race: isLoggedIn() returned true above but the player ref can be null + // mid-tick. Legacy Rs2Npc.getNpcsForPlayer returns Stream.empty() in that case; + // mirror that with an empty snapshot so resetLastAttack does nothing useful instead + // of matching every "non-interacting" NPC. + if (localPlayer == null) { + npcsRef.set(new ArrayList<>()); + return; + } + npcsRef.set(Microbot.getRs2NpcCache().query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .toList()); usePrayer = config.togglePrayer(); flickQuickPrayer = config.toggleQuickPray(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java index 804b746d79..a6f48de6df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java @@ -1,23 +1,25 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Player; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j public class PrayerScript extends Script { + public boolean run(AIOFighterConfig config) { try { Rs2NpcManager.loadJson(); @@ -38,7 +40,15 @@ private void handlePrayer(AIOFighterConfig config) { if (!Microbot.isLoggedIn() || !config.togglePrayer()) return; if (config.prayerStyle() != PrayerStyle.CONTINUOUS && config.prayerStyle() != PrayerStyle.ALWAYS_ON) return; if (config.prayerStyle() == PrayerStyle.CONTINUOUS) { - boolean underAttack = Rs2Npc.getNpcsForPlayer(npc -> !npc.isDead() && npc.getCombatLevel() > 1).findAny().isPresent() || Rs2Combat.inCombat(); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + // Logout race: isLoggedIn() returned true above but the player ref can be null + // mid-tick. Legacy Rs2Npc.getNpcsForPlayer guards this; we have to as well. + if (localPlayer == null) return; + boolean underAttack = Microbot.getRs2NpcCache().query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> !npc.isDead() && npc.getCombatLevel() > 1) + .first() != null + || Rs2Combat.inCombat(); if(!underAttack) { Rs2Prayer.disableAllPrayers(); return; @@ -47,7 +57,10 @@ private void handlePrayer(AIOFighterConfig config) { return; } - Rs2NpcModel npc = Rs2Npc.getNpcsForPlayer(n -> !n.isDead() && n.getCombatLevel() > 1).findFirst().orElse(null); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> Objects.equals(n.getInteracting(), localPlayer)) + .where(n -> !n.isDead() && n.getCombatLevel() > 1) + .nearest(); if (npc == null) { return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java index 73cabbcb71..fb14a39fbe 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java @@ -9,10 +9,9 @@ import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.aiofighter.enums.State; import net.runelite.client.plugins.microbot.aiofighter.model.InventorySetupUtil; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.MonsterLocation; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.skills.slayer.Rs2Slayer; import java.util.Arrays; @@ -24,6 +23,7 @@ public class SlayerScript extends Script { static WorldPoint cachedMonsterLocation = null; static String cachedMonsterLocationName = null; AIOFighterConfig config; + @SneakyThrows public boolean run(AIOFighterConfig config) { this.config = config; @@ -94,9 +94,17 @@ public void handleSlayerTask() { reset(); AIOFighterPlugin.setState(State.GETTING_TASK); if(Rs2Slayer.walkToSlayerMaster(config.slayerMaster())) { - Rs2NpcModel npc = Rs2Npc.getNpc(config.slayerMaster().getName()); + // Preserve legacy partial-name match (Rs2Npc.getNpc uses contains, not equals). + // Single getName() fetch per NPC — each call is a client-thread invoke. + final String masterName = config.slayerMaster().getName().toLowerCase(); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> { + String name = n.getName(); + return name != null && name.toLowerCase().contains(masterName); + }) + .nearest(); if(npc != null) { - Rs2Npc.interact(npc, "Assignment"); + npc.click("Assignment"); sleepUntil(Rs2Slayer::hasSlayerTask, 5000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java index 1a92a71e6f..6fc7720d92 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java @@ -1,8 +1,8 @@ package net.runelite.client.plugins.microbot.aiofighter.model; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcStats; public class Monster { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java index 3abb174e14..94884eb16f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java @@ -6,6 +6,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; +import net.runelite.client.plugins.microbot.aiofighter.enums.State; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; @@ -25,6 +26,7 @@ public boolean run(AIOFighterConfig config) { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; if (!config.useSafety()) return; + if (isBankingOrWalking()) return; if (config.missingRunes() && config.useMagic() && !Rs2Magic.hasRequiredRunes(config.magicSpell())){ stopAndLog("Missing runes for spell: " + config.magicSpell()); } @@ -58,11 +60,18 @@ public boolean run(AIOFighterConfig config) { public void stopAndLog(String reason) { log(reason, Level.WARNING); - if(Rs2Bank.walkToBank()){ - Rs2Player.logout(); - Plugin PlayerAssistPlugin = Microbot.getPlugin(AIOFighterPlugin.class.getName()); - Microbot.stopPlugin(PlayerAssistPlugin); + // Avoid competing with BankerScript's walker while it is already controlling movement. + if (!isBankingOrWalking() && !Rs2Player.isMoving()) { + Rs2Bank.walkToBank(); } + Rs2Player.logout(); + Plugin PlayerAssistPlugin = Microbot.getPlugin(AIOFighterPlugin.class.getName()); + Microbot.stopPlugin(PlayerAssistPlugin); + } + + private boolean isBankingOrWalking() { + State state = AIOFighterPlugin.getState(); + return state == State.BANKING || state == State.WALKING; } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java index e8dbbd94b6..dfec9b1ca2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java @@ -77,7 +77,7 @@ AIOMagicConfig provideConfig(ConfigManager configManager) { @Inject private SpinFlaxScript spinFlaxScript; - public final static String version = "1.2.6"; + public final static String version = "1.2.7"; @Override protected void startUp() throws AWTException { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java index 47b2660399..c33c009c94 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java @@ -63,7 +63,7 @@ public boolean run() { Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (targetNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java index 65c57babdc..b250a516cf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java @@ -107,7 +107,7 @@ public boolean run() { } else { var configuredNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (configuredNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java index 918da17350..12300b0da7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java @@ -69,7 +69,7 @@ public boolean run() { } else { var configuredNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (configuredNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java index 1070eea7c9..f2911a9ff1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java @@ -104,7 +104,7 @@ public boolean run() { // 1) STUN target from user config using queryable NPC cache var target = Microbot.getRs2NpcCache().query() .withName(configuredTargetNpc) - .nearest(); + .nearestOnClientThread(); if (target == null) { attemptNpcRecoveryTeleport(configuredTargetNpc); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java index d341a043dd..a1944a0ccb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java @@ -1,12 +1,11 @@ package net.runelite.client.plugins.microbot.animatedarmour; -import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; @@ -59,9 +58,9 @@ public boolean run(AnimatedArmourConfig config) { public void animateArmor() { WorldPoint armorStandLocation = new WorldPoint(2851, 3536, 0); - GameObject armorStand = Rs2GameObject.getGameObject(armorStandLocation); + Rs2TileObjectModel armorStand = Microbot.getRs2TileObjectCache().query().within(armorStandLocation, 0).nearest(); if (armorStand != null) { - Rs2GameObject.interact(armorStand); + armorStand.click(); Rs2Player.waitForAnimation(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java index f83c14d3cf..69f6028f0e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java @@ -24,7 +24,7 @@ ) public class ArceuusRcPlugin extends Plugin { - public static final String version = "1.0.2"; + public static final String version = "1.0.3"; @Getter @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java index 487af9a1ad..1fc511ed38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; @@ -12,7 +11,6 @@ import net.runelite.client.plugins.microbot.arceuusrc.enums.Altar; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -223,9 +221,9 @@ public String getAltarName() { } public void useAltar() { - final GameObject altar = Rs2GameObject.getGameObject(getAltarName(), true, 11); + var altar = Microbot.getRs2TileObjectCache().query().withName(getAltarName()).within(11).nearestOnClientThread(); if (altar != null) { - if (Rs2GameObject.interact(altar,"Bind")) Rs2Inventory.waitForInventoryChanges(6_000); + if (altar.click("Bind")) Rs2Inventory.waitForInventoryChanges(6_000); hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); } } @@ -303,10 +301,10 @@ public boolean chipEssenceFast() { } public void useDarkAltar() { - final GameObject darkAltar = Rs2GameObject.getGameObject(DARK_ALTAR, true, 11); + var darkAltar = Microbot.getRs2TileObjectCache().query().withName(DARK_ALTAR).within(11).nearestOnClientThread(); if (darkAltar == null) return; - Rs2GameObject.interact(darkAltar,"Venerate"); + darkAltar.click("Venerate"); sleepUntil(()->!Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK),6_000); } @@ -314,12 +312,12 @@ public void mineEssence() { if(getAltar() == Altar.BLOOD && !Rs2Inventory.hasItem(BLOOD_ESSENCE_ACTIVE)){ Rs2Inventory.interact(BLOOD_ESSENCE, "Activate"); } - final GameObject runeStone = Rs2GameObject.getGameObject(STR_DENSE_RUNESTONE, true, 11); - if (runeStone == null) { // should never happen bc shouldMineEssence checks for the runestone + var runeStone = Microbot.getRs2TileObjectCache().query().withName(STR_DENSE_RUNESTONE).within(11).nearestOnClientThread(); + if (runeStone == null) { Microbot.log("Cannot find runestone"); return; } - Rs2GameObject.interact(runeStone,"Chip"); + runeStone.click("Chip"); // this checks if we are gaining essence from mining final AtomicInteger emptyCount = new AtomicInteger(Rs2Inventory.emptySlotCount()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java b/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java index 0482924117..813c54c485 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java @@ -3,7 +3,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Quest; import net.runelite.api.QuestState; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -14,7 +13,6 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.magic.*; @@ -167,9 +165,9 @@ public boolean run(AstralRunesConfig config) { switchInventoryTabIfNeeded(); var bankTileLoc = !dreamMentorComplete ? SEAL_OF_PASSAGE_BANKER : DREAM_MENTOR_BANKER; - TileObject bankTile = Rs2GameObject.getGameObject(bankTileLoc); + var bankTileModel = Microbot.getRs2TileObjectCache().query().within(bankTileLoc, 0).nearest(); Rs2Walker.walkFastCanvas(LUNAR_ISLE_BANK_WORLD_POINT); - if( bankTile != null && !Rs2Bank.isOpen() ) { + if( bankTileModel != null && !Rs2Bank.isOpen() ) { Rs2Bank.openBank(); updateRuneStates(); if( Rs2Inventory.hasItem(runeItemId) ) { @@ -177,7 +175,7 @@ public boolean run(AstralRunesConfig config) { } } else if( Rs2Player.distanceTo(bankTileLoc) > 2 ) { Rs2Walker.walkFastCanvas(bankTileLoc); - } else if( bankTile == null ) { + } else if( bankTileModel == null ) { Rs2Bank.openBank(); } return; @@ -378,9 +376,7 @@ private static boolean isLunarIsleRegion() { } private static boolean openLunarBank() { - var bankTileLoc = !(Rs2Player.getQuestState(Quest.DREAM_MENTOR) == QuestState.FINISHED) ? SEAL_OF_PASSAGE_BANKER : DREAM_MENTOR_BANKER; - TileObject bankTile = Rs2GameObject.getGameObject(bankTileLoc); - Rs2Bank.openBank(bankTile); + Rs2Bank.openBank(); sleepUntil(Rs2Bank::isOpen); return Rs2Bank.isOpen(); } @@ -389,9 +385,9 @@ private static boolean openLunarBank() { private void setSpellbookLunarAltar() { if( isLunarIsleRegion() ) { Rs2Walker.walkTo(ASTRAL_ALTAR_WORLD_POINT); - var altarGameObject = Rs2GameObject.getGameObject(ASTRAL_ALTAR_ID); - if( altarGameObject != null ) { - Rs2GameObject.interact(altarGameObject, "Pray"); + var altarModel = Microbot.getRs2TileObjectCache().query().withId(ASTRAL_ALTAR_ID).nearest(); + if( altarModel != null ) { + altarModel.click("Pray"); sleepUntil(this::isLunar); Rs2Random.wait(400, 800); canCastMoonclanTeleport = Rs2Spells.MOONCLAN_TELEPORT.hasRequirements() && Rs2Magic.hasRequiredRunes(Rs2Spells.MOONCLAN_TELEPORT); @@ -400,10 +396,10 @@ private void setSpellbookLunarAltar() { } private static void doAltarCraft() { - TileObject altarTile = Rs2GameObject.getGameObject(ASTRAL_ALTAR_WORLD_POINT); - if( altarTile != null && Rs2Player.getWorldLocation().distanceTo(ASTRAL_ALTAR_WORLD_POINT) < 5) { + var altarModel = Microbot.getRs2TileObjectCache().query().within(ASTRAL_ALTAR_WORLD_POINT, 0).nearest(); + if( altarModel != null && Rs2Player.getWorldLocation().distanceTo(ASTRAL_ALTAR_WORLD_POINT) < 5) { if( Rs2Inventory.hasItem(ItemID.BLANKRUNE_HIGH) ) { - Rs2GameObject.interact(altarTile); + altarModel.click(); Rs2Inventory.waitForInventoryChanges(800); } if( !Rs2Inventory.allPouchesEmpty() ) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java index 4710ff2822..485e98733c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java @@ -2,24 +2,21 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; @Slf4j public class AutoChompyKillerScript extends Script { @@ -31,22 +28,20 @@ public class AutoChompyKillerScript extends Script { private AutoChompyKillerConfig config; private boolean isBloatedToadOnGround() { - Stream npcs = Rs2Npc.getNpcs(); - long toadCount = npcs.filter(element -> element.getWorldLocation().equals(Rs2Player.getWorldLocation()) && element.getId() == NpcID.BLOATED_TOAD).count(); + long toadCount = Microbot.getRs2NpcCache().query().withId(NpcID.BLOATED_TOAD).where(element -> element.getWorldLocation().equals(Rs2Player.getWorldLocation())).count(); return toadCount > 0; } private boolean isDeadChompyNearby() { - Stream npcs = Rs2Npc.getNpcs(); - long deadChompyCount = npcs.filter(element -> element.getId() == NpcID.CHOMPYBIRD_DEAD && Rs2Player.getWorldLocation().distanceTo(element.getWorldLocation()) <= 5).count(); + long deadChompyCount = Microbot.getRs2NpcCache().query().withId(NpcID.CHOMPYBIRD_DEAD).where(element -> Rs2Player.getWorldLocation().distanceTo(element.getWorldLocation()) <= 5).count(); return deadChompyCount > 0; } private Rs2NpcModel getNearestReachableNpc(int npcId) { Rs2WorldPoint playerLocation = new Rs2WorldPoint(Rs2Player.getWorldLocation()); - return Rs2Npc.getNpcs(npc -> npc.getId() == npcId) + return Microbot.getRs2NpcCache().query().withId(npcId).toList().stream() .min(java.util.Comparator.comparingInt(npc -> playerLocation.distanceToPath(npc.getWorldLocation()))) .orElse(null); @@ -258,7 +253,7 @@ public boolean run(AutoChompyKillerConfig config) { case FILLING_BELLOWS: log.info("State: FILLING_BELLOWS"); Microbot.status = "Filling bellows"; - if (Rs2GameObject.interact(ObjectID.SWAMPBUBBLES, "Suck")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.SWAMPBUBBLES, "Suck")) { boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 5000); if (completed) { log.info("Successfully filled bellows"); @@ -321,7 +316,7 @@ public boolean run(AutoChompyKillerConfig config) { } log.info("Found swamp toad, attempting to inflate it"); - if (Rs2Npc.interact(swampToad, "Inflate")) { + if (swampToad.click("Inflate")) { // wait for interaction to start boolean interactionStarted = sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); if (interactionStarted) { @@ -359,7 +354,7 @@ public boolean run(AutoChompyKillerConfig config) { Rs2NpcModel targetChompy = getNearestReachableNpc(NpcID.CHOMPYBIRD); if (targetChompy != null) { log.info("Attacking chompy"); - if (Rs2Npc.interact(targetChompy, "Attack")) { + if (targetChompy.click("Attack")) { sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 10000); log.info("Attack animation completed: {}", completed); @@ -378,7 +373,7 @@ public boolean run(AutoChompyKillerConfig config) { Rs2NpcModel deadChompy = getNearestReachableNpc(NpcID.CHOMPYBIRD_DEAD); if (deadChompy != null) { log.info("Plucking dead chompy"); - if (Rs2Npc.interact(deadChompy, "Pluck")) { + if (deadChompy.click("Pluck")) { sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 5000); log.info("Plucking animation completed: {}", completed); @@ -447,13 +442,13 @@ public void handlePetReceived(boolean logoutOnCompletion) { } public void handleCantReachBubbles() { - List bubbles = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.SWAMPBUBBLES); + var bubbles = Microbot.getRs2TileObjectCache().query().withId(ObjectID.SWAMPBUBBLES).toList(); if (bubbles != null && !bubbles.isEmpty()) { Random rand = new Random(); - GameObject bubble = bubbles.get(rand.nextInt(bubbles.size())); - + var bubble = bubbles.get(rand.nextInt(bubbles.size())); + if (bubble != null) { - Rs2GameObject.interact(bubble, "Suck"); + bubble.click("Suck"); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); state = AutoChompyKillerState.INFLATING; } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java index d78880e27a..de7bafc1b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java @@ -25,7 +25,7 @@ ) @Slf4j public class AutoEssenceMiningPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private AutoEssenceMiningConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java index 017e59755b..f72a9cb168 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.autoessencemining; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; @@ -10,10 +9,8 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -166,10 +163,10 @@ private void handleTeleportingWithAubury() { } // find Aubury NPC - Rs2NpcModel aubury = Rs2Npc.getNpc("Aubury"); + Rs2NpcModel aubury = Microbot.getRs2NpcCache().query().withName("Aubury").nearestOnClientThread(); if (aubury != null) { log.info("Found Aubury, attempting teleport"); - if (Rs2Npc.interact(aubury, "Teleport")) { + if (aubury.click("Teleport")) { log.info("Clicked teleport, waiting for animation"); Rs2Player.waitForAnimation(3000); log.info("Teleport animation completed"); @@ -199,11 +196,11 @@ private void handleMiningEssence() { } // find essence rock to mine - GameObject essenceRock = Rs2GameObject.getGameObject("Rune Essence", false); - + var essenceRock = Microbot.getRs2TileObjectCache().query().withName("Rune Essence").nearestOnClientThread(); + if (essenceRock != null) { log.info("Found rune essence rock, attempting to mine"); - if (Rs2GameObject.interact(essenceRock, "Mine")) { + if (essenceRock.click("Mine")) { log.info("Started mining essence, waiting for XP drop"); boolean xpGained = Rs2Player.waitForXpDrop(Skill.MINING, true); if (xpGained) { @@ -233,11 +230,11 @@ private void handleUsingPortal() { } // find the portal to exit - GameObject portal = Rs2GameObject.getGameObject("Portal", false); + var portal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearestOnClientThread(); if (portal != null) { log.info("Found portal, attempting to use it"); - if (Rs2GameObject.interact(portal)) { + if (portal.click()) { log.info("Clicked portal, waiting for teleport animation"); Rs2Player.waitForAnimation(3000); log.info("Successfully used portal to exit essence mine"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java index 0cbe3c61bd..9c17b350da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java @@ -2,8 +2,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.gameval.ObjectID; import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.ObjectID; import net.runelite.api.Skill; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; @@ -22,7 +22,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -124,9 +124,9 @@ private void handleFishing() { } activateSpec(); if (fishAction.isEmpty()) { - fishAction = Rs2Npc.getAvailableAction(fishingSpot, selectedFish.getActions()); + fishAction = Rs2Npc.getAvailableAction(new net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel(fishingSpot.getNpc()), selectedFish.getActions()); } - if (!fishAction.isEmpty() && Rs2Npc.interact(fishingSpot, fishAction)) { + if (!fishAction.isEmpty() && fishingSpot.click(fishAction)) { Rs2Player.waitForXpDrop(Skill.FISHING); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -241,9 +241,9 @@ private void handleErrorRecovery() { */ private Rs2NpcModel findNearestFishingSpot() { int[] spotIds = selectedFish.getFishingSpot(); - return Rs2Npc.getNpcs(npc -> Arrays.stream(spotIds).anyMatch(id -> npc.getId() == id)) - .findFirst() - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> Arrays.stream(spotIds).anyMatch(id -> npc.getId() == id)) + .nearest(); } private List getRawFishInInventory() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java index f440db95a1..2fba62212d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java @@ -14,8 +14,8 @@ import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -86,8 +86,7 @@ public void onGameTick(GameTick event) { Rs2Prayer.toggle(nextPrayer, true); } - Rs2NpcModel hunllef = Rs2Npc.getNpcs() - .filter(npc -> HUNLLEF_IDS.contains(npc.getId())) + Rs2NpcModel hunllef = Microbot.getRs2NpcCache().query().where(npc -> HUNLLEF_IDS.contains(npc.getId())).toList().stream() .findFirst() .orElse(null); diff --git a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java index 2c58428c25..251067020f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java @@ -19,7 +19,7 @@ @PluginDescriptor( name = PluginConstants.CRANNY + "Bagged Plants", description = "Cranny's Bagged Plant Planter", - version = "1.0.0", + version = "1.0.1", minClientVersion = "1.9.8", tags = {"skilling", "construction", "farming"}, enabledByDefault = PluginConstants.DEFAULT_ENABLED, diff --git a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java index 67ca1f6d72..5015653778 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java @@ -1,16 +1,16 @@ package net.runelite.client.plugins.microbot.baggedplants; -import net.runelite.api.TileObject; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.baggedplants.enums.BaggedPlantsState; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -114,12 +114,12 @@ private void calculateState(BaggedPlantsConfig config) { if (inHouse && hasUnnotedPlants) { // Check if there's a built plant to remove first - TileObject builtPlant = Rs2GameObject.findObjectById(BUILT_PLANT); + var builtPlant = Microbot.getRs2TileObjectCache().query().withId(BUILT_PLANT).nearest(); if (builtPlant != null) { state = BaggedPlantsState.REMOVE_PLANT; } else { // No built plant, check for empty plant space to build on - TileObject plantSpace = Rs2GameObject.findObjectById(PLANT_SPACE); + var plantSpace = Microbot.getRs2TileObjectCache().query().withId(PLANT_SPACE).nearest(); if (plantSpace != null) { state = BaggedPlantsState.BUILD_PLANT; } @@ -168,7 +168,7 @@ private boolean hasUsableWateringCans() { private boolean isInHouse() { // Similar to gilded altar script - check if Phials NPC is not present - return Rs2Npc.getNpc("Phials") == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread() == null; } private void checkInventory() { @@ -179,9 +179,9 @@ private void checkInventory() { } private void enterHouse(BaggedPlantsConfig config) { - TileObject housePortal = Rs2GameObject.findObjectById(HOUSE_PORTAL); + var housePortal = Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL).nearest(); if (housePortal != null) { - if (Rs2GameObject.interact(housePortal, "Build mode")) { + if (housePortal.click("Build mode")) { System.out.println("Entered house in build mode"); // Wait the configured time int waitTime = Rs2Random.between(config.minWaitTime() * 1000, config.maxWaitTime() * 1000); @@ -195,9 +195,9 @@ private void enterHouse(BaggedPlantsConfig config) { } private void buildPlant() { - TileObject plantSpace = Rs2GameObject.findObjectById(PLANT_SPACE); + var plantSpace = Microbot.getRs2TileObjectCache().query().withId(PLANT_SPACE).nearest(); if (plantSpace != null) { - if (Rs2GameObject.interact(plantSpace, "Build")) { + if (plantSpace.click("Build")) { System.out.println("Interacted with plant space to build"); sleepUntilOnClientThread(this::hasBuildInterfaceOpen, 2500); Rs2Keyboard.keyPress('1'); // Select first option @@ -216,9 +216,9 @@ private void buildPlant() { } private void removePlant() { - TileObject builtPlant = Rs2GameObject.findObjectById(BUILT_PLANT); + var builtPlant = Microbot.getRs2TileObjectCache().query().withId(BUILT_PLANT).nearest(); if (builtPlant != null) { - if (Rs2GameObject.interact(builtPlant, "Remove")) { + if (builtPlant.click("Remove")) { System.out.println("Interacted with plant to remove"); sleepUntilOnClientThread(this::hasRemoveInterfaceOpen, 2500); Rs2Keyboard.keyPress('1'); // Confirm removal @@ -244,7 +244,7 @@ private void refillSupplies() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use(NOTED_BAGGED_PLANT); } else { - Rs2Npc.interact("Phials", "Use"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); Rs2Player.waitForWalking(); } return; // Wait for dialogue to open @@ -285,7 +285,7 @@ private void refillSupplies() { } if (wateringCanToRefill != -1) { - TileObject well = Rs2GameObject.findObjectById(SINK); + var well = Microbot.getRs2TileObjectCache().query().withId(SINK).nearest(); if (well != null) { System.out.println("Refilling watering can ID: " + wateringCanToRefill + " (Full cans: " + fullWateringCans + "/3)"); final int canToRefill = wateringCanToRefill; diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java index 9dee6d3554..c4c679e13b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java @@ -36,7 +36,7 @@ ) @Slf4j public class BanksShopperPlugin extends Plugin { - public final static String version = "1.4.0"; + public final static String version = "1.4.2"; @Inject private BanksShopperConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java index e5600c8e46..2670596c54 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java @@ -1,15 +1,27 @@ package net.runelite.client.plugins.microbot.banksshopper; +import java.util.Collections; +import java.util.Comparator; +import java.util.concurrent.CompletableFuture; + +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.depositbox.Rs2DepositBox; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2BankID; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.TimeUnit; @@ -23,6 +35,7 @@ public class BanksShopperScript extends Script { private final BanksShopperPlugin plugin; private ShopperState state = ShopperState.SHOPPING; + private WorldPoint shopNpcLocation; public BanksShopperScript(final BanksShopperPlugin plugin) { this.plugin = plugin; @@ -32,6 +45,7 @@ public boolean run(BanksShopperConfig config) { Microbot.pauseAllScripts.compareAndSet(true, false); Microbot.enableAutoRunOn = false; initialPlayerLocation = null; + shopNpcLocation = null; Rs2Antiban.resetAntibanSettings(); Rs2AntibanSettings.naturalMouse = true; @@ -61,11 +75,17 @@ public boolean run(BanksShopperConfig config) { return; } - sleepUntil(() -> Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()), 5000); + if (!Rs2Shop.isOpen() && !ensureShopOpen()) return; boolean successfullAction = false; boolean outOfStock = false; if (Rs2Shop.isOpen()) { + // First successful interaction pins the return-trip target to the + // NPC's actual tile, not whatever tile the user toggled the plugin on. + if (shopNpcLocation == null) { + Rs2NpcModel npc = Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()); + if (npc != null) shopNpcLocation = npc.getWorldLocation(); + } for (String itemName : plugin.getItemNames()) { if (!isRunning() || Microbot.pauseAllScripts.get()) break; if (itemName.length() <= 1) continue; @@ -128,6 +148,11 @@ public boolean run(BanksShopperConfig config) { } break; case BANKING: + if (tryDirectBankDeposit()) { + state = ShopperState.SHOPPING; + return; + } + // No reachable bank in the loaded scene — fall back to the walker. if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), initialPlayerLocation)) return; state = ShopperState.SHOPPING; @@ -156,11 +181,120 @@ public void shutdown() { state = ShopperState.SHOPPING; // Reset state to SHOPPING for next run initialPlayerLocation = null; // Reset initial player location + shopNpcLocation = null; Rs2Antiban.resetAntibanSettings(); super.shutdown(); } + /** + * Fast-path bank deposit: if a bank booth or banker is loaded in the current scene + * and the pathfinder can reach its tile, click it directly so the game server handles + * pathing — no Rs2Walker invocation. After depositing, only invoke the walker for the + * return trip if the shop NPC isn't itself loaded + reachable from the new position. + * + * @return true when the bank was opened + deposited via direct interaction; false to + * signal the caller should fall back to the walker-based round trip. + */ + private boolean tryDirectBankDeposit() { + // Closest in-scene Rs2BankID-matched object — booths use "Bank", chests use "Use", + // deposit boxes use "Deposit". Same gate the agent server uses ("reachable" = same + // WorldView as player): if Rs2GameObject.getAll returns it, it's in scene. Clicking + // the default left-click action lets the server walk the player to an adjacent tile + // and open the appropriate UI (bank widget or deposit-box widget). + boolean alreadyOpen = Rs2Bank.isOpen() || Rs2DepositBox.isOpen(); + if (!alreadyOpen) { + TileObject bankObject = Rs2GameObject.getAll(o -> Rs2BankID.BANK_ID_SET.contains(o.getId())).stream() + .min(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) + .orElse(null); + + if (bankObject != null) { + Microbot.log("[BanksShopper] direct-click bank id=" + bankObject.getId() + + " at " + bankObject.getWorldLocation() + + " from " + Rs2Player.getWorldLocation()); + if (!Rs2GameObject.interact(bankObject)) return false; + if (!sleepUntil(() -> Rs2Bank.isOpen() || Rs2DepositBox.isOpen(), 15_000)) { + Microbot.log("[BanksShopper] click sent but no bank/deposit UI opened"); + return false; + } + } else { + Rs2NpcModel banker = Rs2Npc.getBankerNPC(); + if (banker == null || !Rs2Bank.openBank(banker)) return false; + } + } + + if (Rs2Bank.isOpen()) { + for (String itemName : plugin.getItemNames()) { + if (itemName.matches("\\d+")) { + Rs2Bank.depositAll(Integer.parseInt(itemName)); + } else { + Rs2Bank.depositAll(itemName, false); + } + } + Rs2Bank.closeBank(); + } else if (Rs2DepositBox.isOpen()) { + for (String itemName : plugin.getItemNames()) { + if (itemName.matches("\\d+")) { + Rs2DepositBox.depositAll(Integer.parseInt(itemName)); + } else { + Rs2DepositBox.depositAll(Collections.singletonList(itemName)); + } + } + Rs2DepositBox.closeDepositBox(); + } else { + return false; + } + + // Return-trip walker is owned by SHOPPING (see ensureShopOpen): drive only when the + // NPC is missing from the loaded scene, then cancel + await so the visual route + // disappears immediately and the next SHOPPING tick can issue a clean Trade click. + return true; + } + + /** + * One-shot "open the shop" gate for the SHOPPING state. Handles the two failure modes + * separately so we never re-click while waiting: + *
    + *
  • NPC not loaded in the scene → start a background {@code Rs2Walker.walkTo} + * toward {@link #shopNpcLocation} (captured on the first trade), poll for the + * NPC to enter the scene, then {@code clearWalkingRoute} + await the async + * thread so the ShortestPath overlay clears and no late {@code currentTarget} + * write resurrects the route.
  • + *
  • NPC in scene but shop UI not yet open → send exactly one + * {@code Rs2Npc.interact(npc, "Trade")} and wait. The previous polling pattern + * ({@code sleepUntil(() -> Rs2Shop.openShop(...), 5000)}) re-invoked + * {@code Rs2Npc.interact} every scheduler tick, queuing redundant Trade clicks + * while the player was still walking to the NPC.
  • + *
+ * @return true when the shop UI is open and the caller can proceed with trades; + * false to defer to the next scheduler tick. + */ + private boolean ensureShopOpen() { + Rs2NpcModel npc = Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()); + if (npc == null) { + WorldPoint walkTarget = shopNpcLocation != null ? shopNpcLocation : initialPlayerLocation; + if (walkTarget == null) return false; + + CompletableFuture walkFuture = CompletableFuture.runAsync( + () -> Rs2Walker.walkTo(walkTarget, 4)); + sleepUntil(() -> Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()) != null + || Rs2Player.getWorldLocation().distanceTo(walkTarget) <= 4, + 30_000); + Rs2Walker.clearWalkingRoute("banksshopper:shop-npc-in-range"); + try { + walkFuture.get(2, TimeUnit.SECONDS); + } catch (Exception ignored) { + // walker thread already exited (most common) or interrupted — either way + // we've cleared the route; let the next tick retry. + } + return false; + } + + Rs2Npc.interact(npc, "Trade"); + sleepUntil(Rs2Shop::isOpen, 5000); + return Rs2Shop.isOpen(); + } + /** * Hops to a new world */ diff --git a/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java index 9b8cc77f99..bd0a99e9e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java @@ -14,7 +14,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -68,10 +68,10 @@ public boolean run(BarbarianFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if(Rs2Npc.interact(fishingspot, "Use-rod")) { + if(fishingspot.click("Use-rod")) { Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); }; @@ -85,13 +85,9 @@ public void onGameTick() { } private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : FishingSpot.BARB_FISH.getIds()) { - Rs2NpcModel fishingSpot = Rs2Npc.getNpc(fishingSpotId); - if (fishingSpot != null) { - return fishingSpot; - } - } - return null; + return Microbot.getRs2NpcCache().query() + .withIds(FishingSpot.BARB_FISH.getIds()) + .nearest(); } private void dropInventoryItems(BarbarianFishingConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java b/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java index b3b5265c6e..93e8417b5f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.barbarianvillagefisher; import net.runelite.api.Skill; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.game.FishingSpot; import net.runelite.client.plugins.microbot.Microbot; @@ -14,11 +13,10 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -130,10 +128,10 @@ public boolean run(BarbarianVillageFisherConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingSpot.getLocalLocation())) { - validateInteractable(fishingSpot); + validateInteractable(fishingSpot.getNpc()); } - if (Rs2Npc.interact(fishingSpot, fishingAction)) { + if (fishingSpot.click(fishingAction)) { debug("Interacted with fishing spot"); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -255,13 +253,9 @@ private void determineState(BarbarianVillageFisherConfig config, BarbarianFishin // Locate the fishing spot and return the NPC private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : FishingSpot.SALMON.getIds()) { - var fishingSpot = Rs2Npc.getNpc(fishingSpotId); - if (fishingSpot != null) { - return fishingSpot; - } - } - return null; + return Microbot.getRs2NpcCache().query() + .withIds(FishingSpot.SALMON.getIds()) + .nearest(); } // Process for walking to the bank @@ -344,12 +338,11 @@ private boolean closeToLocation(WorldPoint location) { } private boolean isGameObjectOnTile(WorldPoint location, int id) { - // Return true if the specified tile contains the desired ID. - TileObject tile = Rs2GameObject.findGameObjectByLocation(location); - if (tile != null && id == tile.getId()) { - return true; - } - return false; + var result = Microbot.getRs2TileObjectCache().query() + .withId(id) + .within(location, 0) + .first(); + return result != null; } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java index 168940c873..9827169efb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class BarrowsPlugin extends Plugin { - public static final String version = "2.0.3"; + public static final String version = "2.0.9"; @Inject private BarrowsConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index 848f14097e..71a3c9d55a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -18,6 +18,7 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.util.equipment.JewelleryLocationEnum; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -27,8 +28,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -150,6 +150,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() == ItemID.TELEPORT_TO_HOUSE) { if (!inTunnels && !shouldBank && Rs2Player.getWorldLocation().distanceTo(new WorldPoint(3573, 3296, 0)) > 60) { + if(Rs2Bank.isOpen()){ + closeBank(); + return; + } //needed to intercept the walker if(rs2TileObjectCache.query().withId(4525).nearest() == null){ Rs2Inventory.interact("Teleport to house", "Inside"); @@ -448,6 +452,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { WhoisTun = "Unknown"; inTunnels = false; } else { + if(Rs2Bank.isOpen()){ + closeBank(); + return; + } Rs2Inventory.interact("Teleport to house", "Inside"); sleepUntil(() -> Rs2Player.getWorldLocation().getY() < 9600 || Rs2Player.getWorldLocation().getY() > 9730, Rs2Random.between(6000, 10000)); ChestsOpened++; @@ -468,7 +476,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { stopFutureWalker(); //tele out outOfSupplies(config); - //walk to and open the bank Rs2Bank.walkToBankAndUseBank(BankLocation.FEROX_ENCLAVE); BreakHandlerScript.lockState.set(false); } else { @@ -694,36 +701,61 @@ public void closeBank(){ } public void handlePOH(BarrowsConfig config){ - if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() == ItemID.TELEPORT_TO_HOUSE){ - Rs2TileObjectModel pohThing = rs2TileObjectCache.query().withId(4525).nearest(); - if(pohThing != null){ - Microbot.log("We're in our POH"); - Rs2TileObjectModel rejPool = rs2TileObjectCache.query().withIds(29238,29239,29241,29240).nearest(); - if(rejPool != null){ - if(rejPool.click("Drink")){ - sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); - sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); - } + if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() != ItemID.TELEPORT_TO_HOUSE){ + return; + } + Client client = Microbot.getClient(); + if(client == null){ + return; + } + WorldView worldView = client.getTopLevelWorldView(); + if(worldView == null){ + return; + } + if(!worldView.isInstance()){ + return; + } + Rs2TileObjectModel pohThing = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + if(pohThing == null){ + return; + } + Microbot.log("We're in our POH"); + Rs2TileObjectModel rejPool = rs2TileObjectCache.query().withIds(29238,29239,29241,29240).nearestOnClientThread(); + if(rejPool != null){ + if(rejPool.click("Drink")){ + sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); + sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); + } + } + Rs2TileObjectModel regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearestOnClientThread(); + if(regularPortal != null){ + for(int pohPortalAttempts = 0; pohPortalAttempts < 40; pohPortalAttempts++){ + if(!super.isRunning()){ + break; } - Rs2TileObjectModel regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearest(); - if(regularPortal != null){ - while(pohThing != null){ - if(!super.isRunning()){break;} - if(!Rs2Player.isMoving()){ - if(regularPortal.click("Enter")){ - sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); - sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); - sleepUntil(()-> rs2TileObjectCache.query().withIds(37603,37615,37591).nearest() == null, Rs2Random.between(10000,15000)); - } - } - } - + pohThing = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + if(pohThing == null){ + break; + } + regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearestOnClientThread(); + if(regularPortal == null){ + break; + } + if(Rs2Player.isMoving()){ + sleep(Rs2Random.between(200, 600)); + continue; + } + if(regularPortal.click("Enter")){ + sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); + sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); + sleepUntil(()-> rs2TileObjectCache.query().withIds(37603,37615,37591).nearestOnClientThread() == null, Rs2Random.between(10000,15000)); } else { - // we have a nexus 33410 - Microbot.log("No nexus support yet, shutting down"); - super.shutdown(); + break; } } + } else { + Microbot.log("No nexus support yet, shutting down"); + super.shutdown(); } } @@ -814,7 +846,7 @@ public void goToTheMound(Rs2WorldArea moundArea){ //strange old man body blocking us - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearest(); + Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearestOnClientThread(); if(strangeOldMan !=null){ if(strangeOldMan.getWorldLocation() != null){ @@ -879,7 +911,7 @@ public void gainRP(BarrowsConfig config){ if(RP>870) return; - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearest(); + Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearestOnClientThread(); if(skele == null || skele.isDead()) return; @@ -926,7 +958,7 @@ public void gainRP(BarrowsConfig config){ if(hintNpcModel()!=null) { Rs2NpcModel barrowsbrotherHint = hintNpcModel(); - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel brother = rs2NpcCache.query().withName(barrowsbrotherHint.getName()).nearest(); + Rs2NpcModel brother = rs2NpcCache.query().withName(barrowsbrotherHint.getName()).nearest(); if(brother !=null && brother.hasLineOfSight()) { Microbot.log("The brother is here."); break; @@ -1106,15 +1138,98 @@ public void antiPatternDropVials(){ } public void outOfSupplies(BarrowsConfig config){ suppliesCheck(config); - // Needed because the walker won't teleport to the enclave while in the tunnels or in a barrow - if(shouldBank && (inTunnels || Rs2Player.getWorldLocation().getPlane() == 3)){ - if(Rs2Equipment.interact(EquipmentInventorySlot.RING, "Ferox Enclave")){ - Microbot.log("We're out of supplies. Teleporting."); - if(inTunnels) inTunnels=false; - sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); - sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + if(!shouldBank){ + return; + } + boolean needFeroxRingTeleport = false; + if(inTunnels){ + needFeroxRingTeleport = true; + } + if(Rs2Player.getWorldLocation().getPlane() == 3){ + needFeroxRingTeleport = true; + } + if(isInPlayerOwnedHouse()){ + needFeroxRingTeleport = true; + } + if(!needFeroxRingTeleport){ + return; + } + if(tryFeroxTeleportViaRingOfDueling()){ + Microbot.log("We're out of supplies. Teleporting to Ferox Enclave."); + if(inTunnels){ + inTunnels = false; } + sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); + sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + } + } + + private boolean isInPlayerOwnedHouse(){ + Client c = Microbot.getClient(); + if(c == null){ + return false; } + WorldView wv = c.getTopLevelWorldView(); + if(wv == null){ + return false; + } + if(!wv.isInstance()){ + return false; + } + if(inTunnels){ + return false; + } + Rs2TileObjectModel portal = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + return portal != null; + } + + private boolean tryFeroxTeleportViaRingOfDueling(){ + Rs2ItemModel equippedRing = Rs2Equipment.get(EquipmentInventorySlot.RING); + if(equippedRing != null){ + String equippedName = equippedRing.getName(); + if(equippedName != null){ + if(equippedName.contains("Ring of dueling")){ + if(Rs2Equipment.interact(EquipmentInventorySlot.RING, "Ferox Enclave")){ + return true; + } + } + } + } + int[] duelingRingIds = new int[]{ + ItemID.RING_OF_DUELING1, + ItemID.RING_OF_DUELING2, + ItemID.RING_OF_DUELING3, + ItemID.RING_OF_DUELING4, + ItemID.RING_OF_DUELING5, + ItemID.RING_OF_DUELING6, + ItemID.RING_OF_DUELING7, + ItemID.RING_OF_DUELING8 + }; + for(int idx = duelingRingIds.length - 1; idx >= 0; idx--){ + int ringId = duelingRingIds[idx]; + if(!Rs2Inventory.hasItem(ringId)){ + continue; + } + if(tryRubInventoryRingToFerox(ringId)){ + return true; + } + } + return false; + } + + private boolean tryRubInventoryRingToFerox(int ringId){ + String feroxLabel = JewelleryLocationEnum.FEROX_ENCLAVE.getDestination(); + if(Rs2Inventory.interact(ringId, feroxLabel)){ + return true; + } + if(Rs2Inventory.interact(ringId, "Rub")){ + sleepUntil(() -> Rs2Dialogue.hasDialogueOption(feroxLabel), Rs2Random.between(1500, 3500)); + if(Rs2Dialogue.clickOption(feroxLabel)){ + return true; + } + return Rs2Dialogue.clickOption(feroxLabel, false); + } + return false; } public void disablePrayer(){ if(Rs2Random.between(0,100) >= Rs2Random.between(0,5)) { @@ -1194,7 +1309,7 @@ public void checkForAndFightBrother(BarrowsConfig config){ if(inTunnels) { - if (!Rs2Npc.hasLineOfSight(currentBrother)) { + if (!currentBrother.hasLineOfSight()) { Microbot.log("No LOS!"); break; } @@ -1209,12 +1324,12 @@ public void checkForAndFightBrother(BarrowsConfig config){ } if(hintNpcModel() != null && Rs2Player.getInteracting() != null && !Rs2Player.getInteracting().getName().equals(hintNpcModel().getName())){ - if(Rs2Npc.attack(currentBrother)){ + if(currentBrother.click("Attack")){ sleepUntil(()-> Rs2Player.isInCombat(), Rs2Random.between(3000,6000)); } } else { if(!Rs2Player.isInCombat()){ - if(Rs2Npc.attack(currentBrother)){ + if(currentBrother.click("Attack")){ sleepUntil(()-> Rs2Player.isInCombat(), Rs2Random.between(3000,6000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java index 9666210874..48a0005ea6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java @@ -86,4 +86,45 @@ default boolean goToBank() { return false; } + @ConfigItem( + keyName = "startGiantSeaweedAfter", + name = "Start Giant Seaweed after run", + description = "When the birdhouse run finishes, start the TaF Giant Seaweed plugin (if installed)", + section = optionsSection, + position = 1 + ) + default boolean startGiantSeaweedAfter() { + return false; + } + + @ConfigSection( + name = "Debug", + description = "Debug and testing options", + position = 3, + closedByDefault = true + ) + String debugSection = "debug"; + + @ConfigItem( + keyName = "enableOverrideStartState", + name = "Enable Override Start State", + description = "When enabled, the plugin will skip to the selected state on startup", + section = debugSection, + position = 0 + ) + default boolean enableOverrideStartState() { + return false; + } + + @ConfigItem( + keyName = "overrideStartState", + name = "Override Start State", + description = "Skip to a specific state instead of starting from the beginning", + section = debugSection, + position = 1 + ) + default FornBirdhouseRunsInfo.states overrideStartState() { + return FornBirdhouseRunsInfo.states.GEARING; + } + } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java index bebef016db..795fc3e042 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class FornBirdhouseRunsPlugin extends Plugin { - final static String version = "1.1.0"; + final static String version = "1.1.4"; @Provides FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(FornBirdhouseRunsConfig.class); diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index d3d598a179..50756b3ac6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -2,10 +2,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.api.Quest; import net.runelite.api.QuestState; -import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; + import net.runelite.client.Notifier; import net.runelite.client.config.Notification; import net.runelite.client.plugins.microbot.Microbot; @@ -18,13 +19,21 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.shortestpath.Restriction; +import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; + +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import javax.inject.Inject; import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.birdhouseruns.FornBirdhouseRunsInfo.*; @@ -34,8 +43,50 @@ public class FornBirdhouseRunsScript extends Script { private static final WorldPoint birdhouseLocation2 = new WorldPoint(3768, 3761, 0); private static final WorldPoint birdhouseLocation3 = new WorldPoint(3677, 3882, 0); private static final WorldPoint birdhouseLocation4 = new WorldPoint(3679, 3815, 0); + private static final WorldPoint SOUTH_ROWBOAT = new WorldPoint(3724, 3807, 0); + private static final WorldPoint VERDANT_MUSHTREE = new WorldPoint(3757, 3757, 0); + private static final int MUSHTREE_OBJECT_ID = 30924; + // Each location maps to a BIRDHOUSE_TRANSMIT_* varp. See isEmpty/isBuilt/isSeeded + // below for the canonical state decoding (matches RuneLite's BirdHouseState). + private static final int VARP_HOUSE_1 = VarPlayerID.BIRDHOUSE_TRANSMIT_D; // Verdant SW + private static final int VARP_HOUSE_2 = VarPlayerID.BIRDHOUSE_TRANSMIT_C; // Verdant NE + private static final int VARP_HOUSE_3 = VarPlayerID.BIRDHOUSE_TRANSMIT_A; // Meadow N + private static final int VARP_HOUSE_4 = VarPlayerID.BIRDHOUSE_TRANSMIT_B; // Meadow S + private static final int ARRIVAL_RADIUS = 4; + private static final int SCENE_INTERACT_RANGE = 25; + // Canonical Fossil Island region IDs (matches RuneLite's BirdHouseTracker). + private static final java.util.Set FOSSIL_ISLAND_REGIONS = java.util.Set.of( + 14650, 14651, 14652, 14906, 14907, 14908, 15162, 15163); + // Single source of truth: a birdhouse-accepted seed is one whose item name + // (lowercased) is in this set. Bank lookup, inventory lookup, and seed pick + // all match the same way — no id-list drift, no placeholder/variant gotchas. + // Set lists every allotment/hop/flower seed birdhouses accept (Farming + // level ≤ 35, per OSRS Wiki). + private static final Set BIRDHOUSE_SEED_NAMES = Set.of( + "potato seed", + "onion seed", + "cabbage seed", + "tomato seed", + "sweetcorn seed", + "strawberry seed", + "barley seed", + "hammerstone seed", + "asgarnian seed", + "jute seed", + "yanillian seed", + "krandorian seed", + "wildblood seed", + "marigold seed", + "rosemary seed", + "nasturtium seed", + "woad seed", + "limpwurt seed" + ); + private static final long STATE_STALL_TIMEOUT_MS = 120_000L; private boolean initialized; private String setupErrorMessage = ""; + private states lastObservedStatus; + private long stateEnteredAtMs; @Inject private Notifier notifier; private final FornBirdhouseRunsPlugin plugin; @@ -61,8 +112,12 @@ public boolean run() { return; } initialized = true; - - if (config.useInventorySetup()) { + + states startOverride = config.enableOverrideStartState() ? config.overrideStartState() : states.GEARING; + if (startOverride != states.GEARING) { + log.info("Override start state → {}", startOverride); + botStatus = startOverride; + } else if (config.useInventorySetup()) { boolean hasInventorySetup = config.inventorySetup() != null && Rs2InventorySetup.isInventorySetup(config.inventorySetup().getName()); if (hasInventorySetup) { var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); @@ -80,94 +135,171 @@ public boolean run() { this.shutdown(); return; } - } else { - // Auto bank withdrawal + } else if (!hasRequiredInventory()) { + // Auto bank withdrawal — only if inventory isn't already prepared. if (!setupManualInventory()) { log.error("Birdhouse run failed: {}", setupErrorMessage); this.shutdown(); return; } + } else { + log.info("Inventory already prepared — skipping bank trip"); + } + if (startOverride == states.GEARING) { + botStatus = states.TELEPORTING; } - botStatus = states.TELEPORTING; } if (!super.run()) return; - switch (botStatus) { - case TELEPORTING: - Rs2Walker.walkTo(new WorldPoint(3764, 3869, 1), 5); - botStatus = states.VERDANT_TELEPORT; - break; - case VERDANT_TELEPORT: - interactWithObject(30920); - sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null); - Rs2Widget.clickWidget(39845895); - sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation1) < 20); - botStatus = states.DISMANTLE_HOUSE_1; - break; - case DISMANTLE_HOUSE_1: - dismantleBirdhouse(30568, states.BUILD_HOUSE_1); - break; - case BUILD_HOUSE_1: - buildBirdhouse(birdhouseLocation1, states.SEED_HOUSE_1); - break; - case SEED_HOUSE_1: - seedHouse(birdhouseLocation1, states.DISMANTLE_HOUSE_2); - case DISMANTLE_HOUSE_2: - dismantleBirdhouse(30567, states.BUILD_HOUSE_2); - break; - case BUILD_HOUSE_2: - buildBirdhouse(birdhouseLocation2, states.SEED_HOUSE_2); - break; - case SEED_HOUSE_2: - seedHouse(birdhouseLocation2, states.MUSHROOM_TELEPORT); - break; - case MUSHROOM_TELEPORT: - interactWithObject(30924); - sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null); - Rs2Widget.clickWidget(39845903); - sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation3) < 20); - botStatus = states.DISMANTLE_HOUSE_3; - break; - case DISMANTLE_HOUSE_3: - dismantleBirdhouse(30565, states.BUILD_HOUSE_3); - break; - case BUILD_HOUSE_3: - buildBirdhouse(birdhouseLocation3, states.SEED_HOUSE_3); - break; - case SEED_HOUSE_3: - seedHouse(birdhouseLocation3, states.DISMANTLE_HOUSE_4); - break; - case DISMANTLE_HOUSE_4: - Rs2Walker.walkTo(new WorldPoint(3680, 3813, 0)); - dismantleBirdhouse(30566, states.BUILD_HOUSE_4); - break; - case BUILD_HOUSE_4: - buildBirdhouse(birdhouseLocation4, states.SEED_HOUSE_4); - break; - case SEED_HOUSE_4: - seedHouse(birdhouseLocation4, states.FINISHING); - break; - case FINISHING: - emptyNests(); - - if (config.goToBank()) { - Rs2Walker.walkTo(BankLocation.FOSSIL_ISLAND_WRECK.getWorldPoint()); - if (!Rs2Bank.isOpen()) Rs2Bank.openBank(); - Rs2Bank.depositAll(); - } + if (!Rs2Walker.disableTeleports && isOnFossilIsland()) { + Rs2Walker.disableTeleports = true; + blockRubberCapMushrooms(); + log.info("On Fossil Island — disabling teleports and rubber cap mushrooms for remaining walks"); + } - botStatus = states.FINISHED; - notifier.notify(Notification.ON, "Birdhouse run is finished."); - this.shutdown(); - break; - case FINISHED: + boolean advanced = true; + while (advanced) { + advanced = false; + if (botStatus != lastObservedStatus) { + log.info("State → {} (player at {}, region={}, onFossilIsland={}, inv=[{}])", + botStatus, + Rs2Player.getWorldLocation(), + Rs2Player.getWorldLocation() == null ? "null" : Rs2Player.getWorldLocation().getRegionID(), + isOnFossilIsland(), + dumpInventory()); + lastObservedStatus = botStatus; + stateEnteredAtMs = System.currentTimeMillis(); + } else if (botStatus != states.FINISHED + && System.currentTimeMillis() - stateEnteredAtMs > STATE_STALL_TIMEOUT_MS) { + log.error("Birdhouse run stalled in state {} for >{}ms — player at {}, inv=[{}] — aborting", + botStatus, STATE_STALL_TIMEOUT_MS, + Rs2Player.getWorldLocation(), dumpInventory()); + shutdown(); + return; + } + switch (botStatus) { + case TELEPORTING: + case VERDANT_TELEPORT: + if (!isOnFossilIsland()) { + if (!teleportToFossilIsland()) break; + Rs2Walker.disableTeleports = true; + } + Rs2Walker.walkTo(birdhouseLocation1); + botStatus = states.DISMANTLE_HOUSE_1; + advanced = true; + break; + case DISMANTLE_HOUSE_1: + if (dismantleBirdhouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.BUILD_HOUSE_1; + advanced = true; + } + break; + case BUILD_HOUSE_1: + if (buildBirdhouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.SEED_HOUSE_1; + advanced = true; + } + break; + case SEED_HOUSE_1: + if (seedHouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.DISMANTLE_HOUSE_2; + advanced = true; + } + break; + case DISMANTLE_HOUSE_2: + if (dismantleBirdhouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.BUILD_HOUSE_2; + advanced = true; + } + break; + case BUILD_HOUSE_2: + if (buildBirdhouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.SEED_HOUSE_2; + advanced = true; + } + break; + case SEED_HOUSE_2: + if (seedHouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.MUSHROOM_TELEPORT; + advanced = true; + } + break; + case MUSHROOM_TELEPORT: + Rs2GameObject.interact(MUSHTREE_OBJECT_ID, "Use"); + sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null, 5000); + Rs2Widget.clickWidget("Mushroom Meadow"); + sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation3) < 20, 10000); + botStatus = states.DISMANTLE_HOUSE_3; + advanced = true; + break; + case DISMANTLE_HOUSE_3: + if (dismantleBirdhouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.BUILD_HOUSE_3; + advanced = true; + } + break; + case BUILD_HOUSE_3: + if (buildBirdhouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.SEED_HOUSE_3; + advanced = true; + } + break; + case SEED_HOUSE_3: + if (seedHouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.DISMANTLE_HOUSE_4; + advanced = true; + } + break; + case DISMANTLE_HOUSE_4: + if (dismantleBirdhouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.BUILD_HOUSE_4; + advanced = true; + } + break; + case BUILD_HOUSE_4: + if (buildBirdhouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.SEED_HOUSE_4; + advanced = true; + } + break; + case SEED_HOUSE_4: + if (seedHouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.FINISHING; + advanced = true; + } + break; + case FINISHING: + emptyNests(); + + if (config.goToBank()) { + Rs2Walker.walkTo(SOUTH_ROWBOAT, 3); + Rs2Walker.walkTo(BankLocation.FOSSIL_ISLAND_WRECK.getWorldPoint()); + if (!Rs2Bank.isOpen()) Rs2Bank.openBank(); + Rs2Bank.depositAll(); + } + + botStatus = states.FINISHED; + notifier.notify(Notification.ON, "Birdhouse run is finished."); + log.info("Birdhouse run finished — disabling plugin."); + + if (config.startGiantSeaweedAfter()) { + log.info("Starting Giant Seaweed Farmer plugin"); + if (!Microbot.startPlugin("net.runelite.client.plugins.microbot.GiantSeaweedFarmer.GiantSeaweedFarmerPlugin")) { + log.warn("Failed to start Giant Seaweed Farmer — is it installed?"); + } + } + Microbot.stopPlugin(plugin); + break; + case FINISHED: + break; + } } } catch (Exception ex) { log.error("Error in birdhouse run script", ex); } - }, 0, 1000, TimeUnit.MILLISECONDS); + }, 0, 600, TimeUnit.MILLISECONDS); return true; } @@ -192,74 +324,327 @@ private void emptyNests() { @Override public void shutdown() { super.shutdown(); + Rs2Walker.disableTeleports = false; + ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles(); initialized = false; botStatus = states.TELEPORTING; + lastObservedStatus = null; + stateEnteredAtMs = 0L; + } + + private static void blockRubberCapMushrooms() { + Restriction[] restrictions = new Restriction[] { + new Restriction(3663, 3808, 0), + new Restriction(3664, 3808, 0), + new Restriction(3665, 3808, 0), + new Restriction(3666, 3809, 0), + new Restriction(3666, 3810, 0) + }; + ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles(restrictions); } - private boolean interactWithObject(int objectId) { - Rs2GameObject.interact(objectId); - sleepUntil(Rs2Player::isInteracting); - sleepUntil(() -> !Rs2Player.isInteracting()); + /** Throttle for arrivedAndStill log lines (one per second per target). */ + private long lastArrivedLogMs; + private WorldPoint lastArrivedLogTarget; + + private boolean arrivedAndStill(WorldPoint loc) { + WorldPoint pos = Rs2Player.getWorldLocation(); + int dist = Rs2Player.distanceTo(loc); + if (dist <= ARRIVAL_RADIUS) { + return true; + } + boolean moving = Rs2Player.isMoving(); + long now = System.currentTimeMillis(); + boolean logThisTick = !loc.equals(lastArrivedLogTarget) || now - lastArrivedLogMs >= 1000; + if (moving) { + if (logThisTick) { + log.info("arrivedAndStill[{}]: moving (at {}, dist={})", loc, pos, dist); + lastArrivedLogMs = now; + lastArrivedLogTarget = loc; + } + return false; + } + if (logThisTick) { + log.info("arrivedAndStill[{}]: not arrived (at {}, dist={}); walking via WebWalker (stop at {})", + loc, pos, dist, SCENE_INTERACT_RANGE); + lastArrivedLogMs = now; + lastArrivedLogTarget = loc; + } + Rs2Walker.walkTo(loc, SCENE_INTERACT_RANGE); + return false; + } + + // Canonical state predicates, matching BirdHouseState.fromVarpValue: + // varp == 0 → EMPTY space (need to Build) + // varp > 0, %3 != 0 → BUILT (covers "just built, no seeds" and "seeded, growing") + // varp > 0, %3 == 0 → SEEDED, ready (Empty action available) + private static boolean isEmpty(int varp) { return varp == 0; } + private static boolean isBuilt(int varp) { return varp > 0 && varp % 3 != 0; } + private static boolean isSeeded(int varp) { return varp > 0 && varp % 3 == 0; } + + /** Click Empty on the birdhouse at {@code loc}. Wait for varp to hit 0. */ + private boolean dismantleBirdhouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (!isSeeded(varp)) { + log.info("Dismantle[{}]: varp={} not seeded (empty={}, built={}) — skipping", + varpId, varp, isEmpty(varp), isBuilt(varp)); + return true; + } + log.info("Dismantle[{}]: varp={} → Empty at {}", varpId, varp, loc); + if (!Rs2GameObject.interact(loc, "Empty")) { + if (!arrivedAndStill(loc)) return false; + log.warn("Dismantle[{}]: object not found at {} after arriving", varpId, loc); + return false; + } + if (!sleepUntil(() -> isEmpty(Microbot.getVarbitPlayerValue(varpId)), 10000)) { + log.warn("Dismantle[{}]: timeout waiting for varp→0 after Empty click; varp={} (player at {})", + varpId, Microbot.getVarbitPlayerValue(varpId), Rs2Player.getWorldLocation()); + return false; + } + log.info("Dismantle[{}]: success (varp=0)", varpId); + return true; + } + + /** Click Build at {@code loc}. Game auto-combines hammer+log. Wait for varp != 0. */ + private boolean buildBirdhouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (!isEmpty(varp)) { + log.info("Build[{}]: varp={} not empty (built={}, seeded={}) — skipping", + varpId, varp, isBuilt(varp), isSeeded(varp)); + return true; + } + int logCount = Rs2Inventory.count(config.logType().getItemId()); + if (logCount == 0) { + log.error("Build[{}]: no {} (id={}) in inventory — aborting. Inventory: [{}]", + varpId, config.logType().getItemName(), config.logType().getItemId(), dumpInventory()); + shutdown(); + return false; + } + log.info("Build[{}]: varp=0 → Build at {} (logs in inv: {})", varpId, loc, logCount); + if (!Rs2GameObject.interact(loc, "Build")) { + if (!arrivedAndStill(loc)) return false; + log.warn("Build[{}]: object not found at {} after arriving", varpId, loc); + return false; + } + if (!sleepUntil(() -> !isEmpty(Microbot.getVarbitPlayerValue(varpId)), 15000)) { + log.warn("Build[{}]: timeout waiting for varp!=0 after Build click; varp={} (player at {}, logs={})", + varpId, Microbot.getVarbitPlayerValue(varpId), Rs2Player.getWorldLocation(), + Rs2Inventory.count(config.logType().getItemId())); + return false; + } + log.info("Build[{}]: success (varp={})", varpId, Microbot.getVarbitPlayerValue(varpId)); + return true; + } + + /** Use a seed stack on the birdhouse at {@code loc}. Wait for seeds-down OR varp change. */ + private boolean seedHouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (isEmpty(varp)) { + log.error("Seed[{}]: varp=0, can't seed empty space — aborting. Inventory: [{}]", + varpId, dumpInventory()); + shutdown(); + return false; + } + if (isSeeded(varp)) { + log.info("Seed[{}]: varp={} already seeded — skipping", varpId, varp); + return true; + } + Rs2ItemModel seed = findInventoryBirdhouseSeed(10).orElse(null); + if (seed == null) { + log.error("Seed[{}]: no birdhouse-seed stack of ≥10 — aborting. Inventory: [{}]", + varpId, dumpInventory()); + shutdown(); + return false; + } + int seedId = seed.getId(); + int seedsBefore = seed.getQuantity(); + int varpBefore = varp; + log.info("Seed[{}]: use {} id={} (x{}) on {} (varp before={})", + varpId, seed.getName(), seedId, seedsBefore, loc, varpBefore); + if (!Rs2Inventory.use(seedId)) { + log.warn("Seed[{}]: Rs2Inventory.use({}) returned false. Inventory: [{}]", + varpId, seedId, dumpInventory()); + return false; + } + if (!sleepUntil(() -> Rs2Inventory.getSelectedItemId() == seedId, 2000)) { + log.warn("Seed[{}]: seed not selected within 2s. getSelectedItemId={}, looking for {}", + varpId, Rs2Inventory.getSelectedItemId(), seedId); + return false; + } + log.info("Seed[{}]: seed selected (id={}); clicking birdhouse at {}", varpId, seedId, loc); + if (!Rs2GameObject.interact(loc)) { + if (!arrivedAndStill(loc)) return false; + log.warn("Seed[{}]: object not found at {} after arriving", varpId, loc); + return false; + } + if (!sleepUntil(() -> + findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0) < seedsBefore, + 10000)) { + int seedsNow = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.warn("Seed[{}]: no completion signal within 10s. seeds={} (before {}), varp={} (before {})", + varpId, seedsNow, seedsBefore, + Microbot.getVarbitPlayerValue(varpId), varpBefore); + return false; + } + int seedsAfter = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.info("Seed[{}]: success (varp={}, seeds left={})", + varpId, Microbot.getVarbitPlayerValue(varpId), seedsAfter); return true; } - private void seedHouse(WorldPoint worldPoint, states status) { - Rs2Inventory.use(" seed"); - sleepUntil(Rs2Inventory::isItemSelected); - Rs2GameObject.interact(worldPoint); - sleepUntil(() -> Rs2Widget.findWidget("full of seed") != null, 1000); - botStatus = status; + /** True if {@code item}'s lowercased name is in {@link #BIRDHOUSE_SEED_NAMES}. */ + private static boolean isBirdhouseSeed(Rs2ItemModel item) { + if (item == null) return false; + String name = item.getName(); + return name != null && BIRDHOUSE_SEED_NAMES.contains(name.toLowerCase()); + } + + /** First inventory stack of a birdhouse-accepted seed with quantity ≥ {@code minQty}. */ + private static Optional findInventoryBirdhouseSeed(int minQty) { + return Rs2Inventory.items() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .filter(item -> item.getQuantity() >= minQty) + .findFirst(); + } + + /** First bank stack of a birdhouse-accepted seed with quantity ≥ {@code minQty}. */ + private static Optional findBankBirdhouseSeed(int minQty) { + return Rs2Bank.bankItems().stream() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .filter(item -> item.getQuantity() >= minQty) + .findFirst(); + } + + /** Compact "name×qty(id=...)" listing of every inventory item, for diagnostics. */ + private static String dumpInventory() { + return Rs2Inventory.items() + .map(item -> item.getName() + "×" + item.getQuantity() + "(id=" + item.getId() + ")") + .collect(Collectors.joining(", ")); } - private void buildBirdhouse(WorldPoint worldPoint, states status) { - if (!Rs2Inventory.hasItem("bird house") && Rs2Inventory.hasItem(ItemID.POH_CLOCKWORK_MECHANISM)) { - Rs2Inventory.use(ItemID.HAMMER); - Rs2Inventory.use(" logs"); - Rs2Inventory.waitForInventoryChanges(5000); + private boolean isOnFossilIsland() { + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && FOSSIL_ISLAND_REGIONS.contains(loc.getRegionID()); + } + + private boolean teleportToFossilIsland() { + List pendantIds = Arrays.asList( + ItemID.NECKLACE_OF_DIGSITE_1, + ItemID.NECKLACE_OF_DIGSITE_2, + ItemID.NECKLACE_OF_DIGSITE_3, + ItemID.NECKLACE_OF_DIGSITE_4, + ItemID.NECKLACE_OF_DIGSITE_5 + ); + for (int id : pendantIds) { + if (Rs2Inventory.contains(id)) { + log.info("Rubbing digsite pendant (id={}) to teleport to Fossil Island", id); + if (!Rs2Inventory.interact(id, "Rub")) return false; + if (!sleepUntil(Rs2Dialogue::hasSelectAnOption, 5000)) { + log.warn("Pendant rub did not open destination dialog"); + return false; + } + Rs2Dialogue.clickOption("Fossil Island"); + if (!sleepUntil(this::isOnFossilIsland, 15000)) { + log.warn("Did not arrive on Fossil Island after pendant teleport"); + return false; + } + log.info("Arrived on Fossil Island at {}", Rs2Player.getWorldLocation()); + return true; + } } - Rs2GameObject.interact(worldPoint, "Build"); - sleepUntil(Rs2Player::isAnimating); - botStatus = status; + log.error("No digsite pendant found in inventory"); + shutdown(); + return false; } - private void dismantleBirdhouse(int itemId, states status) { - Rs2GameObject.interact(itemId, "Empty"); - Rs2Player.waitForXpDrop(Skill.HUNTER); - botStatus = status; + /** True if the inventory already has everything a full run needs. The digsite + * pendant is only required when off Fossil Island (its sole purpose is the + * teleport onto the island); on-island, we can just walk. */ + private boolean hasRequiredInventory() { + if (Rs2Inventory.count(ItemID.CHISEL) < 1) { + log.info("hasRequiredInventory: no chisel"); + return false; + } + if (Rs2Inventory.count(ItemID.HAMMER) < 1) { + log.info("hasRequiredInventory: no hammer"); + return false; + } + if (!isOnFossilIsland()) { + boolean hasPendant = + Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_1) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_2) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_3) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_4) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_5) >= 1; + if (!hasPendant) { + log.info("hasRequiredInventory: off-island and no digsite pendant"); + return false; + } + } + int logCount = Rs2Inventory.count(config.logType().getItemId()); + if (logCount < 4) { + log.info("hasRequiredInventory: only {} {} (need 4)", logCount, config.logType().getItemName()); + return false; + } + Optional seed = findInventoryBirdhouseSeed(40); + if (seed.isEmpty()) { + log.info("hasRequiredInventory: no birdhouse-seed stack ≥ 40 in inventory. Inventory: [{}]", + dumpInventory()); + return false; + } + log.info("hasRequiredInventory: OK ({} x{}, {} logs)", + seed.get().getName(), seed.get().getQuantity(), logCount); + return true; } private boolean setupManualInventory() { - // Walk to nearest bank + log.info("setupManualInventory: start (player at {}, onFossilIsland={}, inv=[{}])", + Rs2Player.getWorldLocation(), isOnFossilIsland(), dumpInventory()); Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); - - // Open bank + if (!Rs2Bank.openBank()) { setupErrorMessage = "Could not open bank"; log.error(setupErrorMessage); return false; } sleepUntil(Rs2Bank::isOpen); - - // Deposit all + log.info("setupManualInventory: bank open at {}", Rs2Player.getWorldLocation()); + Rs2Bank.depositAll(); Rs2Inventory.waitForInventoryChanges(5000); - - // Withdraw chisel + log.info("setupManualInventory: after depositAll, inv=[{}]", dumpInventory()); + if (!Rs2Bank.withdrawX(ItemID.CHISEL, 1)) { setupErrorMessage = "Missing chisel in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw hammer + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: chisel withdrawn (inv count={})", Rs2Inventory.count(ItemID.CHISEL)); + if (!Rs2Bank.withdrawX(ItemID.HAMMER, 1)) { setupErrorMessage = "Missing hammer in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw digsite pendant (prefer lower charges) - boolean pendantWithdrawn = false; + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: hammer withdrawn (inv count={})", Rs2Inventory.count(ItemID.HAMMER)); + + // Withdraw digsite pendant (prefer lower charges) — only when off Fossil Island. + // If we're already on the island, the pendant is dead weight; don't burn a charge. + boolean pendantWithdrawn = isOnFossilIsland(); + if (pendantWithdrawn) { + log.info("setupManualInventory: on Fossil Island, skipping pendant withdrawal"); + } List pendantIds = Arrays.asList( ItemID.NECKLACE_OF_DIGSITE_1, ItemID.NECKLACE_OF_DIGSITE_2, @@ -267,27 +652,29 @@ private boolean setupManualInventory() { ItemID.NECKLACE_OF_DIGSITE_4, ItemID.NECKLACE_OF_DIGSITE_5 ); - - for (int pendantId : pendantIds) { - if (!isRunning()) break; - if (Rs2Bank.withdrawX(pendantId, 1)) { - pendantWithdrawn = true; - break; + + if (!pendantWithdrawn) { + for (int pendantId : pendantIds) { + if (!isRunning()) break; + if (Rs2Bank.withdrawX(pendantId, 1)) { + Rs2Inventory.waitForInventoryChanges(2000); + pendantWithdrawn = true; + log.info("setupManualInventory: pendant withdrawn (id={})", pendantId); + break; + } } } - + if (!pendantWithdrawn) { setupErrorMessage = "Missing digsite pendant in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw logs + Log selectedLogType = config.logType(); - // Check if bank has enough logs first - int logCount = Rs2Bank.count(selectedLogType.getItemId()); - if (logCount < 4) { - setupErrorMessage = "Need 4 " + selectedLogType.getItemName().toLowerCase() + " but only have " + logCount + " in bank"; + int bankLogCount = Rs2Bank.count(selectedLogType.getItemId()); + if (bankLogCount < 4) { + setupErrorMessage = "Need 4 " + selectedLogType.getItemName().toLowerCase() + " but only have " + bankLogCount + " in bank"; log.error(setupErrorMessage); return false; } @@ -296,49 +683,54 @@ private boolean setupManualInventory() { log.error(setupErrorMessage); return false; } - - // Withdraw seeds (smart selection) - boolean seedsWithdrawn = withdrawSeeds(); - if (!seedsWithdrawn) { + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: 4× {} withdrawn (inv count={})", + selectedLogType.getItemName(), Rs2Inventory.count(selectedLogType.getItemId())); + + if (!withdrawSeeds()) { // setupErrorMessage is set in withdrawSeeds return false; } - - // Close bank + Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - - log.info("Inventory setup complete - starting birdhouse run"); + + log.info("setupManualInventory: complete. Final inv=[{}]", dumpInventory()); return true; } private boolean withdrawSeeds() { - // Priority list of seeds for birdhouses - List seedIds = Arrays.asList( - ItemID.POTATO_SEED, - ItemID.ONION_SEED, - ItemID.CABBAGE_SEED, - ItemID.TOMATO_SEED, - ItemID.BARLEY_SEED, - ItemID.HAMMERSTONE_HOP_SEED, - ItemID.YANILLIAN_HOP_SEED, - ItemID.KRANDORIAN_HOP_SEED - ); - - for (int seedId : seedIds) { - if (!isRunning()) break; - // Check if bank has enough BEFORE trying to withdraw - if (Rs2Bank.count(seedId) >= 40) { - if (Rs2Bank.withdrawX(seedId, 40)) { - log.info("Withdrew 40 of seed ID: {}", seedId); - return true; - } - } + // Log every birdhouse-eligible seed stack the bank has, so we can see + // both what was picked AND what the alternatives were. + String bankSeedSummary = Rs2Bank.bankItems().stream() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .map(item -> item.getName() + "×" + item.getQuantity() + "(id=" + item.getId() + ")") + .collect(Collectors.joining(", ")); + log.info("withdrawSeeds: bank birdhouse-seed stacks: [{}]", bankSeedSummary); + + Rs2ItemModel bankSeed = findBankBirdhouseSeed(40).orElse(null); + if (bankSeed == null) { + setupErrorMessage = "Need 40 seeds but no birdhouse seed type has 40+ in bank"; + log.error(setupErrorMessage); + return false; } - - // If we get here, no seed type had 40+ available - setupErrorMessage = "Need 40 seeds but no birdhouse seed type has 40+ in bank"; - log.error(setupErrorMessage); - return false; + int invBefore = Rs2Inventory.count(bankSeed.getId()); + log.info("withdrawSeeds: selected {} (id={}, bank qty={}); inv before withdraw: {} of id={}", + bankSeed.getName(), bankSeed.getId(), bankSeed.getQuantity(), invBefore, bankSeed.getId()); + if (!Rs2Bank.withdrawX(bankSeed.getId(), 40)) { + setupErrorMessage = "Failed to withdraw 40 " + bankSeed.getName(); + log.error(setupErrorMessage); + return false; + } + Rs2Inventory.waitForInventoryChanges(3000); + int invAfter = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.info("withdrawSeeds: withdrew 40 {} (id={}); inv seed qty = {}", + bankSeed.getName(), bankSeed.getId(), invAfter); + if (invAfter < 40) { + setupErrorMessage = "Withdrew seeds but only got " + invAfter + " (need 40)"; + log.error(setupErrorMessage); + return false; + } + return true; } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java index a2e547e407..4a8bddb9c3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java @@ -17,16 +17,6 @@ default boolean buryBones() { return true; } - @ConfigItem( - keyName = "keepItemNames", - name = "Keep Item Names", - description = "Comma-separated list of item names that should not be dropped", - position = 3 - ) - default String keepItemNames() { - return "Bird snare"; - } - @ConfigItem( keyName = "huntingRadiusValue", name = "Hunting radius", diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java index 57f7045b26..75425fddb5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java @@ -1,16 +1,32 @@ package net.runelite.client.plugins.microbot.birdhunter; import com.google.inject.Provides; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.ObjectID; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.hunter.HunterTrap; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.ui.overlay.OverlayManager; import javax.inject.Inject; +import java.time.Instant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; @PluginDescriptor( name = PluginDescriptor.zerozero + "Bird Hunter", @@ -26,7 +42,10 @@ @Slf4j public class BirdHunterPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; + + @Inject + private Client client; @Inject private BirdHunterConfig config; @@ -40,6 +59,10 @@ public class BirdHunterPlugin extends Plugin { @Inject private OverlayManager overlayManager; + @Getter + private final Map traps = new HashMap<>(); + private WorldPoint lastTickLocalPlayerLocation; + @Provides BirdHunterConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(BirdHunterConfig.class); @@ -47,8 +70,20 @@ BirdHunterConfig provideConfig(ConfigManager configManager) { @Override protected void startUp() { + // Seed lastTickLocalPlayerLocation on the client thread before the + // script loop can fire a layBirdSnare — otherwise the first snare's + // GameObjectSpawned event sees a null baseline, the trap is never + // recorded as owned, and the filter/setTrap path puts the bot in an + // infinite movePlayerOffObject loop on its own untracked trap. + // startUp runs on the AWT EDT; reading player location from there + // throws "must be called on client thread". + lastTickLocalPlayerLocation = Microbot.getClientThread().runOnClientThreadOptional(() -> { + Player lp = client.getLocalPlayer(); + return lp != null ? lp.getWorldLocation() : null; + }).orElse(null); + if (config.startScript()) { - birdHunterScript.run(config); + birdHunterScript.run(config, this); this.overlayManager.add(this.birdHunterOverlay); } } @@ -57,13 +92,14 @@ protected void startUp() { protected void shutDown() { this.overlayManager.remove(this.birdHunterOverlay); birdHunterScript.shutdown(); + traps.clear(); } @Subscribe public void onConfigChanged(ConfigChanged event) { if (event.getGroup().equals("birdhunter") && event.getKey().equals("startScript")) { if (config.startScript()) { - birdHunterScript.run(config); + birdHunterScript.run(config, this); } else { birdHunterScript.shutdown(); } @@ -72,4 +108,82 @@ public void onConfigChanged(ConfigChanged event) { birdHunterScript.updateHuntingArea(config); } } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) { + final GameObject go = event.getGameObject(); + final WorldPoint trapLocation = go.getWorldLocation(); + final HunterTrap myTrap = traps.get(trapLocation); + + switch (go.getId()) { + // Empty placed snare — ownership decision point. Player location is + // updated before this event fires, so we compare the spawn tile to + // the PREVIOUS tick's player location. distance == 0 means the snare + // spawned on the exact tile the player stood on last tick, i.e. ours. + case ObjectID.HUNTING_OJIBWAY_TRAP: + if (lastTickLocalPlayerLocation != null + && trapLocation.distanceTo(lastTickLocalPlayerLocation) == 0) { + traps.put(trapLocation, new HunterTrap(go)); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.FULL); + myTrap.resetTimer(); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.EMPTY); + myTrap.resetTimer(); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_FAILING: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.TRANSITION); + } + break; + } + } + + @Subscribe + public void onGameTick(GameTick event) { + Iterator> it = traps.entrySet().iterator(); + Tile[][][] tiles = client.getScene().getTiles(); + Instant expire = Instant.now().minus(HunterTrap.TRAP_TIME.multipliedBy(2)); + + while (it.hasNext()) { + Map.Entry entry = it.next(); + HunterTrap trap = entry.getValue(); + WorldPoint world = entry.getKey(); + LocalPoint local = LocalPoint.fromWorld(client, world); + + if (local == null) { + if (trap.getPlacedOn().isBefore(expire)) it.remove(); + continue; + } + + GameObject[] objects = tiles[world.getPlane()][local.getSceneX()][local.getSceneY()].getGameObjects(); + boolean anyObject = false; + for (GameObject o : objects) { + if (o != null) { anyObject = true; break; } + } + if (!anyObject) it.remove(); + } + + Player lp = client.getLocalPlayer(); + if (lp != null) lastTickLocalPlayerLocation = lp.getWorldLocation(); + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java index dcc615f836..724b7711f0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.birdhunter; import lombok.Getter; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldArea; @@ -12,21 +11,22 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; public class BirdHunterScript extends Script { @@ -43,13 +43,12 @@ public class BirdHunterScript extends Script { private final Pair boneThresholdRange = Pair.of(3, 10); private final Pair HandleInventoryThresholdRange = Pair.of(18, 25); - public boolean run(BirdHunterConfig config) { + private BirdHunterPlugin plugin; + + public boolean run(BirdHunterConfig config, BirdHunterPlugin plugin) { + this.plugin = plugin; Microbot.log("Bird Hunter script started."); - if (!hasRequiredSnares()) { - Microbot.log("Not enough bird snares in inventory. Stopping the script."); - return false; - } initialStartTile = Rs2Player.getWorldLocation(); randomBoneThreshold = ThreadLocalRandom.current().nextInt(boneThresholdRange.getLeft(), boneThresholdRange.getRight()); @@ -66,6 +65,14 @@ public boolean run(BirdHunterConfig config) { try { if (!super.run() || !Microbot.isLoggedIn()) return; + if (!hasRequiredSnares()) { + int required = getAvailableTraps(Rs2Player.getRealSkillLevel(Skill.HUNTER)); + Microbot.showMessage("Bird Hunter needs at least " + required + + " bird snares in inventory for your Hunter level. Stopping plugin."); + Microbot.stopPlugin(plugin); + return; + } + if (!isInHuntingArea()) { Microbot.log("Player is outside the designated hunting area."); walkBackToArea(); @@ -85,20 +92,21 @@ public boolean run(BirdHunterConfig config) { private boolean hasRequiredSnares() { int hunterLevel = Rs2Player.getRealSkillLevel(Skill.HUNTER); - int allowedSnares = getAvailableTraps(hunterLevel); // Calculate the allowed number of snares + int allowedSnares = getAvailableTraps(hunterLevel); int snaresInInventory = Rs2Inventory.itemQuantity(ItemID.HUNTING_OJIBWAY_BIRD_SNARE); Microbot.log("Allowed snares: " + allowedSnares + ", Snares in inventory: " + snaresInInventory); - return snaresInInventory >= allowedSnares; // Return true if enough snares, false otherwise + return snaresInInventory >= allowedSnares; } public void updateHuntingArea(BirdHunterConfig config) { huntingRadius = config.huntingRadiusValue(); + int side = (2 * huntingRadius) + 1; dynamicHuntingArea = new WorldArea( initialStartTile.getX() - huntingRadius, initialStartTile.getY() - huntingRadius, - (huntingRadius * huntingRadius) + 1, (huntingRadius * huntingRadius) + 1, + side, side, initialStartTile.getPlane() ); } @@ -109,7 +117,7 @@ private boolean isInHuntingArea() { } private void walkBackToArea() { - WorldPoint walkableTile = getSafeWalkableTile(dynamicHuntingArea); + WorldPoint walkableTile = getNearestSafeWalkableTileInArea(dynamicHuntingArea); if (walkableTile != null) { Rs2Walker.walkFastCanvas(walkableTile); @@ -119,31 +127,63 @@ private void walkBackToArea() { } } + private WorldPoint getNearestSafeWalkableTileInArea(WorldArea huntingArea) { + WorldPoint from = Rs2Player.getWorldLocation(); + WorldPoint nearest = null; + int bestDist = Integer.MAX_VALUE; + + for (int x = initialStartTile.getX() - huntingRadius; x <= initialStartTile.getX() + huntingRadius; x++) { + for (int y = initialStartTile.getY() - huntingRadius; y <= initialStartTile.getY() + huntingRadius; y++) { + WorldPoint candidate = new WorldPoint(x, y, huntingArea.getPlane()); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), candidate); + if (localPoint == null || !huntingArea.contains(candidate)) continue; + if (!Rs2Tile.isWalkable(localPoint) || isGameObjectAt(candidate)) continue; + + int dist = from.distanceTo(candidate); + if (dist < bestDist) { + bestDist = dist; + nearest = candidate; + } + } + } + return nearest; + } + private void handleTraps(BirdHunterConfig config) { - List successfulTraps = new ArrayList<>(); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED)); - - List catchingTraps = new ArrayList<>(); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE)); - - List failedTraps = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN); - List idleTraps = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP); - idleTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FAILING)); + List successfulTraps = new ArrayList<>(); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED).toList()); + + List catchingTraps = new ArrayList<>(); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE).toList()); + + List failedTraps = new ArrayList<>(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN).toList()); + List idleTraps = new ArrayList<>(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP).toList()); + idleTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FAILING).toList()); + + // Ownership filter: the plugin records a trap's WorldPoint when it spawns + // on the player's previous-tick tile. Skip everything else — other players' + // snares should not be clicked, and they must not inflate totalTraps below. + Set owned = plugin.getTraps().keySet(); + Predicate mine = t -> owned.contains(t.getWorldLocation()); + successfulTraps.removeIf(mine.negate()); + catchingTraps.removeIf(mine.negate()); + failedTraps.removeIf(mine.negate()); + idleTraps.removeIf(mine.negate()); int availableTraps = getAvailableTraps(Rs2Player.getRealSkillLevel(Skill.HUNTER)); int totalTraps = successfulTraps.size() + failedTraps.size() + idleTraps.size() + catchingTraps.size(); - if (Rs2GroundItem.exists(ItemID.HUNTING_OJIBWAY_BIRD_SNARE, 20)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.HUNTING_OJIBWAY_BIRD_SNARE).within(20).count() > 0) { pickUpBirdSnare(); return; } @@ -154,7 +194,7 @@ private void handleTraps(BirdHunterConfig config) { } if (!successfulTraps.isEmpty()) { - for (GameObject successfulTrap : successfulTraps) { + for (Rs2TileObjectModel successfulTrap : successfulTraps) { if (interactWithTrap(successfulTrap)) { setTrap(config); return; @@ -163,7 +203,7 @@ private void handleTraps(BirdHunterConfig config) { } if (!failedTraps.isEmpty()) { - for (GameObject failedTrap : failedTraps) { + for (Rs2TileObjectModel failedTrap : failedTraps) { if (interactWithTrap(failedTrap)) { setTrap(config); return; @@ -176,7 +216,11 @@ private void handleTraps(BirdHunterConfig config) { private void setTrap(BirdHunterConfig config) { if (!Rs2Inventory.contains(ItemID.HUNTING_OJIBWAY_BIRD_SNARE)) return; - if (Rs2Player.isStandingOnGameObject()) { + // Rs2Player.isStandingOnGameObject() also returns true for ground items + // (dropped loot), which don't actually block snare placement in-game. + // Only skip the tile when there's a real game object on it (existing + // trap, tree, rock). + if (isGameObjectAt(Rs2Player.getWorldLocation())) { if (!movePlayerOffObject()) return; } @@ -197,7 +241,7 @@ private void layBirdSnare() { } private boolean isGameObjectAt(WorldPoint point) { - return Rs2GameObject.findObjectByLocation(point) != null; + return Microbot.getRs2TileObjectCache().query().within(point, 0).count() > 0; } @@ -246,17 +290,23 @@ private boolean movePlayerOffObject() { } - private boolean interactWithTrap(GameObject birdSnare) { - sleep(Rs2Random.randomGaussian(2000, 1250)); - Rs2GameObject.interact(birdSnare); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(7000)); - sleep(Rs2Random.randomGaussian(2000, 1250)); + private boolean interactWithTrap(Rs2TileObjectModel birdSnare) { + if (!plugin.getTraps().containsKey(birdSnare.getWorldLocation())) return false; + // Retry the click until inventory changes (snare returned / loot received). + // Previously a single click with a 7s inventory-changes wait and 2×2s + // gaussian sleeps meant ~13s of stall on a missed click. + int invBefore = Rs2Inventory.count(); + for (int attempt = 0; attempt < 3; attempt++) { + birdSnare.click(); + if (sleepUntil(() -> Rs2Inventory.count() != invBefore, 2500)) break; + } + sleep(Rs2Random.randomGaussian(600, 200)); return false; } private void pickUpBirdSnare() { - if (Rs2GroundItem.loot(ItemID.HUNTING_OJIBWAY_BIRD_SNARE)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.HUNTING_OJIBWAY_BIRD_SNARE).interact("Take")) { sleepUntil(() -> Rs2Inventory.contains(ItemID.HUNTING_OJIBWAY_BIRD_SNARE), 2000); } } @@ -276,11 +326,9 @@ private void checkForBonesAndHandleInventory(BirdHunterConfig config) { } private void handleInventory(BirdHunterConfig config) { - if (config.buryBones() && Rs2Inventory.count("Bones") > randomBoneThreshold) { + if (config.buryBones()) { buryBones(config); - } - buryBones(config); dropItems(config); } @@ -296,14 +344,16 @@ private void buryBones(BirdHunterConfig config) { } } + // Strict drop whitelist. Replaces an earlier dropAllExcept(keepList) that would + // nuke the entire inventory if the keep list was misconfigured. Bird snaring + // only produces Raw bird meat, Bones, and feathers — feathers stack so we let + // them ride; bones are buried when the config is enabled, dropped otherwise. private void dropItems(BirdHunterConfig config) { - String keepItemsConfig = config.keepItemNames(); - List keepItemNames = List.of(keepItemsConfig.split("\\s*,\\s*")); - - if (!keepItemNames.contains("Bird snare")) { - keepItemNames.add("Bird snare"); + if (config.buryBones()) { + Rs2Inventory.dropAll("Raw bird meat"); + } else { + Rs2Inventory.dropAll("Raw bird meat", "Bones"); } - Rs2Inventory.dropAllExcept(keepItemNames.toArray(new String[0])); } public int getAvailableTraps(int hunterLevel) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java index 8cc2cfef28..b433501c3a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java @@ -35,7 +35,7 @@ ) @Slf4j public class BlastoiseFurnacePlugin extends Plugin { - final static String version = "1.2.0"; + final static String version = "1.2.2"; @Inject private BlastoiseFurnaceConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index 8da2474d9d..e61fe7f9db 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -14,14 +14,12 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -31,14 +29,15 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; +import java.util.regex.Pattern; import static net.runelite.api.gameval.ItemID.*; import static net.runelite.api.gameval.ObjectID.*; import static net.runelite.api.gameval.VarbitID.*; -import static net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper.ITEM_NAME_SUFFIX_PATTERN; @Slf4j public class BlastoiseFurnaceScript extends Script { + private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); static final int coalBag = 12019; private static final int MAX_ORE_PER_INTERACTION = 27; private static final int MAX_ORE_PER_HYBRID_INTERACTION = 26; @@ -90,7 +89,7 @@ public boolean run() { return; } Rs2Walker.walkTo(new WorldPoint(2931, 10197, 0)); - Rs2GameObject.interact(DWARF_KELDAGRIM_FACTORY_STAIRS); + Microbot.getRs2TileObjectCache().query().interact(DWARF_KELDAGRIM_FACTORY_STAIRS); return; } @@ -204,8 +203,8 @@ private void handleTax() { sleep(500, 1200); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - Rs2NpcModel blastie = Rs2Npc.getNpc("Blast Furnace Foreman"); - Rs2Npc.interact(blastie, "Pay"); + var blastie = Microbot.getRs2NpcCache().query().withName("Blast Furnace Foreman").nearestOnClientThread(); + if (blastie != null) blastie.click("Pay"); sleepUntil(Rs2Dialogue::isInDialogue, 10000); if (Rs2Dialogue.hasSelectAnOption()) { Rs2Dialogue.clickOption("Yes"); @@ -235,7 +234,7 @@ private void handleDispenserLooting() { } } - Rs2GameObject.interact(BLAST_FURNACE_DISPENSER, "Take"); + Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_DISPENSER, "Take"); sleepUntil(() -> Rs2Widget.hasWidget("What would you like to take?") || @@ -497,7 +496,7 @@ private boolean putOreOnConveyorBelt() { log.error("No ore in Inventory"); return false; } - if (!Rs2GameObject.interact(BLAST_FURNACE_CONVEYER_BELT_CLICKABLE, "Put-ore-on")) { + if (!Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_CONVEYER_BELT_CLICKABLE, "Put-ore-on")) { log.error("Failed to interact with conveyor belt"); return false; } @@ -670,7 +669,7 @@ public void checkAndTopOffCoffer() { sleepUntil(() -> !Rs2Bank.isOpen()); } - Rs2GameObject.interact(BLAST_FURNACE_AUTOMATA_COFFER, "use"); + Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_AUTOMATA_COFFER, "use"); Rs2Player.waitForWalking(2400); if (underfilled && Rs2Dialogue.hasDialogueOption("deposit", false)) { @@ -701,4 +700,3 @@ public boolean fullCoffer() { return coffer == 1; } } - diff --git a/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java b/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java index 6a62ecc94b..4863eef940 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -69,7 +68,7 @@ public boolean run() { case BLESS_AT_ALTAR: BlessedWinePlugin.status = "Blessing wine at altar..."; - Rs2GameObject.interact(52799, "Bless"); + Microbot.getRs2TileObjectCache().query().interact(52799, "Bless"); Rs2Inventory.waitForInventoryChanges(1200); if (!Rs2Inventory.hasItem(BLESSED_WINE)) return; state = BlessedWineState.WALK_TO_BOWL; @@ -85,7 +84,7 @@ public boolean run() { case USE_LIBATION_BOWL: BlessedWinePlugin.status = "Using Libation Bowl..."; - Rs2GameObject.interact(53018, "Fill"); + Microbot.getRs2TileObjectCache().query().interact(53018, "Fill"); if (currentPrayerPoints > 2 && !Rs2Player.isAnimating()) return; if (currentPrayerPoints < 2 && !Rs2Player.isAnimating()) { state = BlessedWineState.WALK_TO_SHRINE; @@ -105,7 +104,7 @@ public boolean run() { case RESTORE_PRAYER: BlessedWinePlugin.status = "Restoring prayer..."; - Rs2GameObject.interact(52405, "Bask"); + Microbot.getRs2TileObjectCache().query().interact(52405, "Bask"); Rs2Player.waitForAnimation(5000); if (currentPrayerPoints != maxPrayerLevel) return; if (!Rs2Inventory.hasItem(BLESSED_WINE)) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java index 8b363d21ec..1c2bd58c22 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java @@ -2,12 +2,11 @@ import lombok.Setter; import net.runelite.api.Client; -import net.runelite.api.NPC; import net.runelite.api.Perspective; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -106,7 +105,7 @@ public Dimension render(Graphics2D graphics) { // Dragon tracking section addSectionDivider("Dragon Tracking"); - NPC nearestDragon = Rs2Npc.getNpc("Blue dragon"); + Rs2NpcModel nearestDragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearestOnClientThread(); boolean isTargeting = nearestDragon != null && script.getCurrentTargetId() != null && script.getCurrentTargetId() == nearestDragon.getId(); @@ -228,12 +227,12 @@ private void addSectionDivider(String sectionName) { ); } - private String getDragonStatus(NPC dragon, boolean isTargeting) { + private String getDragonStatus(Rs2NpcModel dragon, boolean isTargeting) { if (dragon == null) return "No dragons"; return isTargeting ? "Fighting" : "Available"; } - private Color getDragonStatusColor(NPC dragon, boolean isTargeting) { + private Color getDragonStatusColor(Rs2NpcModel dragon, boolean isTargeting) { if (dragon == null) return new Color(169, 169, 169); // Dark Gray return isTargeting ? new Color(220, 20, 60) : new Color(50, 205, 50); // Crimson : Lime Green } diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java index ffcfa38d2d..a02bb431ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java @@ -27,7 +27,7 @@ ) public class BlueDragonsPlugin extends Plugin { - public static final String version = "1.1.2"; + public static final String version = "1.1.3"; static final String CONFIG = "bluedragons"; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java index fa73b2e128..446fce8c30 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java @@ -17,8 +17,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.skillcalculator.skills.MagicAction; @@ -483,14 +482,14 @@ private boolean lootItem(String itemName) { private Rs2NpcModel getAvailableDragon() { - Rs2NpcModel dragon = Rs2Npc.getNpc("Blue dragon"); + Rs2NpcModel dragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearestOnClientThread(); logOnceToChat("Found dragon: " + (dragon != null ? "Yes (ID: " + dragon.getId() + ")" : "No"), true, config); if (dragon != null) { boolean correctId = (dragon.getId() == BLUE_DRAGON_ID_1 || dragon.getId() == BLUE_DRAGON_ID_2 || dragon.getId() == BLUE_DRAGON_ID_3); logOnceToChat("Dragon has correct ID (265, 266, or 267): " + correctId, true, config); - boolean hasLineOfSight = Rs2Npc.hasLineOfSight(new Rs2NpcModel(dragon)); + boolean hasLineOfSight = dragon.hasLineOfSight(); logOnceToChat("Has line of sight to dragon: " + hasLineOfSight, true, config); if (correctId && hasLineOfSight) { @@ -503,13 +502,13 @@ private Rs2NpcModel getAvailableDragon() { private boolean attackDragon(Rs2NpcModel dragon) { final int dragonId = dragon.getId(); - if (Rs2Combat.inCombat() && dragon.getInteracting() != Microbot.getClient().getLocalPlayer()) { + if (Rs2Combat.inCombat() && !dragon.isInteractingWithPlayer()) { logOnceToChat("Cannot attack dragon - player is in combat with different target.", true, config); return false; } - if (Rs2Npc.attack(dragon)) { - boolean dragonKilled = sleepUntil(() -> Rs2Npc.getNpc(dragonId) == null, 60000); + if (dragon.click("Attack")) { + boolean dragonKilled = sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(dragonId).nearest() == null, 60000); if (dragonKilled) { logOnceToChat("Dragon killed. Transitioning to looting state.", true, config); diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java new file mode 100644 index 0000000000..43fda2eae6 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java @@ -0,0 +1,42 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; + +@ConfigInformation( + "Butterfly Catcher — by StonksCode

" + + "Species (net / barehanded):
" + + "Ruby Harvest 5/15  |  Sapphire Glacialis 25/35  |  Snowy Knight 35/45
" + + "Black Warlock 45/55  |  Sunlight Moth 65/75  |  Moonlight Moth 75/85

" + + "Barehanded — XP only, nothing in inventory
" + + "Butterfly Net — equip net first, 10 levels lower requirement" +) +@ConfigGroup("ButterflyCatcher") +public interface ButterflyCatcherConfig extends Config { + + @ConfigItem( + keyName = "butterflyType", + name = "Butterfly / Moth", + description = "Which creature to hunt.
" + + "Classic: Ruby Harvest, Sapphire Glacialis, Snowy Knight, Black Warlock.
" + + "Varlamore: Sunlight Moth, Moonlight Moth.
" + + "Net level shown in parentheses; barehanded = net + 10.", + position = 0 + ) + default ButterflyType butterflyType() { + return ButterflyType.BLACK_WARLOCK; + } + + @ConfigItem( + keyName = "catchMode", + name = "Catch Mode", + description = "BAREHANDED: catch and release for XP — nothing enters inventory.
" + + "BUTTERFLY_NET: use an equipped butterfly net (lower level requirement).", + position = 1 + ) + default CatchMode catchMode() { + return CatchMode.BAREHANDED; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java new file mode 100644 index 0000000000..029c0ad281 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java @@ -0,0 +1,80 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; +import java.awt.*; + +/** + * ===================================================================== + * Butterfly Catcher + * Author: StonksCode + * ===================================================================== + * + * Automates butterfly and moth catching for Hunter XP training. + * + * Supported species: + * - Ruby Harvest (net lvl 5 / bare lvl 15) + * - Sapphire Glacialis (net lvl 25 / bare lvl 35) + * - Snowy Knight (net lvl 35 / bare lvl 45) + * - Black Warlock (net lvl 45 / bare lvl 55) + * - Sunlight Moth (net lvl 65 / bare lvl 75) + * - Moonlight Moth (net lvl 75 / bare lvl 85) + * + * Catch modes: + * BAREHANDED — catch and release for XP; nothing enters inventory. + * BUTTERFLY_NET — equip a butterfly net or magic butterfly net before + * starting; allows catching at 10 levels lower than + * the barehanded requirement. + * + * Usage: + * 1. Stand near a spawn of your chosen species. + * 2. If using Butterfly Net mode, equip your net first. + * 3. Select your species and catch mode in the config panel. + * 4. Start the plugin — it will run indefinitely with no banking. + * ===================================================================== + */ +@PluginDescriptor( + name = PluginConstants.STKS + "Butterfly Catcher", + description = "Automates butterfly and moth catching for Hunter XP. Stand near a spawn, pick your species and mode, start the plugin.", + tags = {"hunter", "butterfly", "moth", "sunlight", "moonlight", "net", "microbot", "stonkscode"}, + authors = {"StonksCode"}, + version = ButterflyCatcherPlugin.version, + minClientVersion = "2.0.8", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class ButterflyCatcherPlugin extends Plugin { + + public static final String version = "1.0.0"; + + @Inject + private ButterflyCatcherConfig config; + + @Inject + private ButterflyCatcherScript script; + + @Provides + ButterflyCatcherConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(ButterflyCatcherConfig.class); + } + + @Override + protected void startUp() throws AWTException { + script.run(config); + log.info("[ButterflyCatcher] Started — target: {}, mode: {}", + config.butterflyType().getDisplayName(), config.catchMode()); + } + + @Override + protected void shutDown() { + script.shutdown(); + log.info("[ButterflyCatcher] Stopped."); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java new file mode 100644 index 0000000000..c0739d4bb9 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java @@ -0,0 +1,169 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * ButterflyCatcherScript + * + * Two modes — both run indefinitely with no banking: + * + * BAREHANDED: + * Click catch, wait for animation/movement to resolve, repeat. + * Requires Hunter level >= target.getBarehandedLevelRequired(). + * + * BUTTERFLY_NET: + * Same loop, but requires a butterfly net or magic butterfly net to be + * equipped. Verified once on startup. Requires Hunter level >= + * target.getNetLevelRequired() (10 levels lower than barehanded). + * + * Item IDs for nets: + * Butterfly net : 10010 + * Magic butterfly net : 11259 + */ +@Slf4j +public class ButterflyCatcherScript extends Script { + + private static final int NET_ITEM_ID = 10010; + private static final int MAGIC_NET_ITEM_ID = 11259; + private static final String TAG = "[ButterflyCatcher]"; + private static final int TICK = 600; + + private ButterflyType targetButterfly; + private CatchMode catchMode; + private boolean catchCommitted = false; + + // ------------------------------------------------------------------------- + // Entry point + // ------------------------------------------------------------------------- + + public boolean run(ButterflyCatcherConfig config) { + this.targetButterfly = config.butterflyType(); + this.catchMode = config.catchMode(); + this.catchCommitted = false; + + int requiredLevel = effectiveLevel(); + Microbot.log(TAG + " Starting — target: " + targetButterfly.getDisplayName() + + " | mode: " + catchMode + + " | Required Hunter level: " + requiredLevel); + + // Net equipment check (BUTTERFLY_NET mode only) + if (catchMode == CatchMode.BUTTERFLY_NET) { + if (!isNetEquipped()) { + Microbot.log(TAG + " No butterfly net equipped! " + + "Equip a Butterfly Net (id 10010) or Magic Butterfly Net (id 11259) " + + "before starting. Stopping."); + Microbot.showMessage("Butterfly Catcher: no butterfly net equipped. Equip one and restart."); + return false; + } + Microbot.log(TAG + " Butterfly net verified."); + } + + // Hunter level check + int hunterLevel = Microbot.getClient().getRealSkillLevel(Skill.HUNTER); + if (hunterLevel < requiredLevel) { + Microbot.log(TAG + " Hunter level too low (" + + hunterLevel + " / " + requiredLevel + + ") for " + targetButterfly.getDisplayName() + + " in " + catchMode + " mode. Stopping."); + Microbot.showMessage("Butterfly Catcher: Hunter level too low (" + + hunterLevel + " / " + requiredLevel + ")."); + return false; + } + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay( + this::tick, 0, TICK, TimeUnit.MILLISECONDS); + return true; + } + + @Override + public void shutdown() { + super.shutdown(); + catchCommitted = false; + } + + // ------------------------------------------------------------------------- + // Main tick + // ------------------------------------------------------------------------- + + private void tick() { + try { + if (!Microbot.isLoggedIn()) return; + if (!super.run()) return; + + // Stop if level drops below threshold (e.g. de-boost) + int hunterLevel = Microbot.getClient().getRealSkillLevel(Skill.HUNTER); + if (hunterLevel < effectiveLevel()) { + Microbot.log(TAG + " Hunter level too low — stopping."); + shutdown(); + return; + } + + tickCatching(); + + } catch (Exception e) { + log.error(TAG + " Unexpected error in tick: {}", e.getMessage(), e); + } + } + + // ------------------------------------------------------------------------- + // Catching logic + // ------------------------------------------------------------------------- + + private void tickCatching() { + // Don't spam-click while already committed to a catch attempt + if (catchCommitted) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + return; + } + catchCommitted = false; + } + + if (Rs2Player.isAnimating()) return; + + // Find nearest target NPC using the singleton cache + var target = Microbot.getRs2NpcCache() + .query() + .withIds(targetButterfly.getNpcIds()) + .nearest(); + + if (target == null) { + Microbot.log(TAG + " No " + targetButterfly.getDisplayName() + + " found nearby (IDs: " + Arrays.toString(targetButterfly.getNpcIds()) + ") — waiting."); + return; + } + + boolean clicked = target.click("Catch"); + if (!clicked) { + log.warn(TAG + " click(Catch) failed on {} at {}", + targetButterfly.getDisplayName(), target.getWorldLocation()); + return; + } + + catchCommitted = true; + log.debug(TAG + " Clicked Catch on {} at {}", + targetButterfly.getDisplayName(), target.getWorldLocation()); + sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isMoving(), 1500); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private int effectiveLevel() { + return catchMode == CatchMode.BUTTERFLY_NET + ? targetButterfly.getNetLevelRequired() + : targetButterfly.getBarehandedLevelRequired(); + } + + private boolean isNetEquipped() { + return Rs2Equipment.isWearing(NET_ITEM_ID) || Rs2Equipment.isWearing(MAGIC_NET_ITEM_ID); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java new file mode 100644 index 0000000000..dabff24f1f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java @@ -0,0 +1,139 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import lombok.Getter; + +/** + * ButterflyType + * + * Defines all catchable butterflies and moths from the Hunter skill. + * Data sourced from: https://oldschool.runescape.wiki/w/Butterfly_(Hunter) + * + * Level requirements: + * The game requires a Hunter level 10 ABOVE the net level for barehanded catching. + * This enum stores the NET level as the baseline; the script derives the + * barehanded level by adding 10. + * + * Species Net Barehanded + * ─────────────────── ──── ────────── + * Ruby Harvest 5 15 + * Sapphire Glacialis 25 35 + * Snowy Knight 35 45 + * Black Warlock 45 55 + * Sunlight Moth 65 75 + * Moonlight Moth 75 85 + * + * Item IDs: + * Butterfly net : 10010 + * Magic butterfly net: 11259 + * Butterfly jar : 10012 (same for all species) + */ +@Getter +public enum ButterflyType { + + RUBY_HARVEST( + "Ruby Harvest", + new int[]{ 5525 }, + 5, + 10012, + 10009 + ), + SAPPHIRE_GLACIALIS( + "Sapphire Glacialis", + new int[]{ 5526 }, + 25, + 10012, + 10011 + ), + SNOWY_KNIGHT( + "Snowy Knight", + new int[]{ 5527 }, + 35, + 10012, + 10013 + ), + BLACK_WARLOCK( + "Black Warlock", + new int[]{ 5553 }, + 45, + 10012, + 10010 + ), + + /** + * Sunlight Moth — Avium Savannah south of the Hunter Guild. + * Net: 65 | Barehanded: 75 + */ + SUNLIGHT_MOTH( + "Sunlight Moth", + new int[]{ 12770 }, + 65, + 10012, + 28890 + ), + + /** + * Moonlight Moth — Neypotzli / Hunter Guild basement / Tonali Cavern. + * Net: 75 | Barehanded: 85 + * NPC IDs: 12771, 12772, 12773 (variants per location). + */ + MOONLIGHT_MOTH( + "Moonlight Moth", + new int[]{ 12771, 12772, 12773 }, + 75, + 10012, + 28893 + ); + + // ------------------------------------------------------------------------- + + /** Human-readable name shown in the config dropdown. */ + private final String displayName; + + /** + * All NPC IDs for this creature. + * Most species have exactly one. Moonlight Moth has three location variants. + */ + private final int[] npcIds; + + /** + * Hunter level required to catch with a butterfly net (or magic butterfly net). + */ + private final int netLevelRequired; + + /** Item ID of the empty butterfly jar (always 10012). */ + private final int jarItemId; + + /** Item ID placed in the inventory after a successful jar catch. */ + private final int caughtItemId; + + // ------------------------------------------------------------------------- + + ButterflyType(String displayName, int[] npcIds, int netLevelRequired, + int jarItemId, int caughtItemId) { + this.displayName = displayName; + this.npcIds = npcIds; + this.netLevelRequired = netLevelRequired; + this.jarItemId = jarItemId; + this.caughtItemId = caughtItemId; + } + + /** + * Hunter level required to catch bare-handed (always netLevelRequired + 10). + */ + public int getBarehandedLevelRequired() { + return netLevelRequired + 10; + } + + /** + * Returns the primary NPC ID (first in the array). Used for logging. + * The script always iterates all IDs in the array when searching. + */ + public int getNpcId() { + return npcIds[0]; + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java new file mode 100644 index 0000000000..f1aef6db59 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java @@ -0,0 +1,37 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +/** + * CatchMode + * + * Controls whether the plugin catches bare-handed (XP only) or uses a + * butterfly net / magic butterfly net. + * + * Banking has been intentionally removed — the script runs indefinitely + * without ever needing to visit a bank. + */ +public enum CatchMode { + + /** + * Catch the butterfly/moth bare-handed. + * The creature is instantly released on catch, so nothing enters the + * inventory. Requires Hunter level = netLevelRequired + 10. + */ + BAREHANDED, + + /** + * Catch using a butterfly net (or magic butterfly net). + * The net must be equipped before the script starts — the script will + * verify this on startup and stop with a message if not equipped. + * Requires Hunter level = netLevelRequired (the lower threshold). + */ + BUTTERFLY_NET; + + @Override + public String toString() { + switch (this) { + case BAREHANDED: return "Barehanded"; + case BUTTERFLY_NET: return "Butterfly Net"; + default: return name(); + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java index 98b2ba4e22..bf4ecc81a5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java @@ -24,7 +24,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class CannonballSmelterPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private CannonballSmelterConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java index a9f28e0cd3..cb2b0b9a13 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java @@ -2,7 +2,6 @@ import net.runelite.api.Client; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,11 +12,9 @@ import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import javax.inject.Inject; @@ -103,14 +100,14 @@ else if (!hasBars() || hasBalls()){ } public void smelt() { - GameObject furnace = Rs2GameObject.getGameObject(config.getFurnace().furnaceID); + Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query().withId(config.getFurnace().furnaceID).nearest(); if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - furnace = Rs2GameObject.getGameObject("Furnace"); + furnace = Microbot.getRs2TileObjectCache().query().withName("Furnace").nearestOnClientThread(); } if (furnace != null) { - Rs2GameObject.interact(furnace, "Smelt"); + furnace.click("Smelt"); Microbot.status = "Moving to furnace..."; sleepUntil(() -> Rs2Widget.getWidget(17694733) != null); if(Rs2Widget.getWidget(17694733) != null) { @@ -138,8 +135,8 @@ public void bank() { if (!isRunning()) break; if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - Rs2NpcModel banker = Rs2Npc.getBankerNPC(); - Rs2Npc.interact(banker, "Bank"); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearestOnClientThread(); + if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); } @@ -170,8 +167,8 @@ public void getMould() { if(!Rs2Inventory.hasItem("ammo mould") && !Rs2Inventory.hasItem("double ammo mould")) { if(!Rs2Bank.isOpen()) { if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - Rs2NpcModel banker = Rs2Npc.getBankerNPC(); - Rs2Npc.interact(banker, "Bank"); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearestOnClientThread(); + if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java index 559241a767..aef0c03b84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ChaosAltarPlugin extends Plugin { - final static String version = "1.1.0"; + final static String version = "1.1.1"; @Inject private ChaosAltarScript chaosAltarScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java index 4875817469..e8b1497a78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java @@ -1,12 +1,12 @@ package net.runelite.client.plugins.microbot.chaosaltar; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; @@ -14,7 +14,6 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2Pvp; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; @@ -98,17 +97,15 @@ public boolean run(ChaosAltarConfig config, ChaosAltarPlugin plugin) { return true; } - private GameObject getChaosAltar() { - return (GameObject) Rs2GameObject - .getAll(obj -> obj.getId() == CHAOS_ALTAR && obj instanceof GameObject) - .stream().findFirst().orElse(null); + private Rs2TileObjectModel getChaosAltar() { + return Microbot.getRs2TileObjectCache().query().withId(CHAOS_ALTAR).nearest(); } public boolean isAtChaosAltar() { - final GameObject gameObject = getChaosAltar(); + final Rs2TileObjectModel gameObject = getChaosAltar(); if (gameObject == null) return false; - final boolean reachable = Rs2GameObject.isReachable(gameObject); + final boolean reachable = gameObject.isReachable(); log.info("Found Chaos Altar GameObject at: {}. Reachable={}", gameObject.getWorldLocation(), reachable); return reachable; } @@ -117,9 +114,8 @@ public boolean isAtChaosAltar() { private void dieToNpc() { Microbot.log("Walking to dangerous NPC to die"); Rs2Walker.walkTo(2979, 3845, 0); - sleepUntil(() -> Rs2Npc.getNpc(CHAOS_FANATIC) != null, 60000); - // Attack chaos fanatic to die - Rs2Npc.attack("Chaos Fanatic"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(CHAOS_FANATIC).nearest() != null, 60000); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Chaos Fanatic").interact("Attack")); // Wait until player dies sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) == 0, 60000); sleepUntil(() -> !Rs2Pvp.isInWilderness(), 15000); @@ -164,7 +160,7 @@ private void offerBones() { if (lastBones != null && isRunning()) { Rs2Inventory.interact(lastBones, "use"); sleep(300, 500); - Rs2GameObject.interact(CHAOS_ALTAR); + Microbot.getRs2TileObjectCache().query().interact(CHAOS_ALTAR); sleep(300, 500); Rs2Inventory.waitForInventoryChanges(Rs2Random.between(500, 2000)); @@ -188,7 +184,7 @@ && isRunning() && Rs2GameObject.exists(CHAOS_ALTAR)) { Rs2Inventory.interact(lastBones, "use"); sleep(100, 300); - Rs2GameObject.interact(CHAOS_ALTAR); + Microbot.getRs2TileObjectCache().query().interact(CHAOS_ALTAR); Rs2Player.waitForXpDrop(Skill.PRAYER); // Small random delay between offerings diff --git a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java index 3868f07eb1..b6d093fa1c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java @@ -28,7 +28,7 @@ ) @Slf4j public class CharterCrafterPlugin extends Plugin { - static final String version = "1.0.0"; + static final String version = "1.0.1"; @Inject private CharterCrafterConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java index 99da91ca0f..c2143dea70 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java @@ -8,8 +8,7 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; @@ -164,7 +163,7 @@ private void bootstrap() { Rs2Inventory.dropAll("Empty light orb", "Light orb"); } - Rs2NpcModel trader = Rs2Npc.getNpc(TRADER_NAME, false); + Rs2NpcModel trader = Microbot.getRs2NpcCache().query().withName(TRADER_NAME).nearestOnClientThread(); if (trader == null) { update("Bootstrap", "Trader not nearby", false, true); state = State.STOP; diff --git a/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java b/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java index 46690b02b0..c3896bc640 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java @@ -12,7 +12,6 @@ import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -145,7 +144,7 @@ public boolean run(AutoRunecraftConfig config) } else { - Rs2GameObject.interact(altar.getAltarRuinsID(), "Enter"); + Microbot.getRs2TileObjectCache().query().withId(altar.getAltarRuinsID()).interact("Enter"); } Rs2Random.wait(800, 1600); sleepUntil(() -> !Rs2Player.isMoving()); @@ -167,7 +166,7 @@ public boolean run(AutoRunecraftConfig config) case EXITING_ALTAR: Microbot.status = "Exiting altar"; - Rs2GameObject.interact(altar.getPortalID(), "Use"); + Microbot.getRs2TileObjectCache().query().withId(altar.getPortalID()).interact("Use"); sleepUntil(() -> !Rs2Player.isMoving()); Rs2Random.wait(800, 1600); state = States.BANKING; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java index 41b811126c..5ae92659d6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java @@ -29,7 +29,7 @@ @PluginDependency(ClueScrollPlugin.class) public class ClueSolverPlugin extends Plugin { - final static String version = "1.0.1"; + final static String version = "1.0.5"; @Inject private ClueSolverScript clueSolverScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java index 87b11d4a1e..6f97c3ccfb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java @@ -3,7 +3,6 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -12,9 +11,8 @@ import net.runelite.client.plugins.cluescrolls.clues.AnagramClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -80,15 +78,16 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) - return; + // v1.0.5 fix: client-thread-safe location read. processGameTick runs in the + // background executor; direct client.getLocalPlayer() throws IllegalStateException. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (hasArrived(player)) { + if (playerLocation.equals(location)) { transitionToInteractionState(); - } else if (isWithinRadius(location, player.getWorldLocation(), 3)) { + } else if (isWithinRadius(location, playerLocation, 3)) { Rs2Walker.walkFastCanvas(location); } break; @@ -134,7 +133,7 @@ private void processGameTick(GameTick event) { private void transitionToInteractionState() { if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpcProvider() != null && Rs2Npc.getNpc(clue.getNpcName(clueScrollPlugin)) != null) { + } else if (clue.getNpcProvider() != null && Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearestOnClientThread() != null) { state = State.INTERACTING_WITH_NPC; } else { log.warn("No valid interaction target found."); @@ -144,11 +143,11 @@ private void transitionToInteractionState() { private boolean interactWithObject() { int targetObject = clue.getObjectId(); - boolean interacted = Rs2GameObject.interact(targetObject, "Search") - || Rs2GameObject.interact(targetObject, "Investigate") - || Rs2GameObject.interact(targetObject, "Examine") - || Rs2GameObject.interact(targetObject, "Look-at") - || Rs2GameObject.interact(targetObject, "Open"); + boolean interacted = Microbot.getRs2TileObjectCache().query().interact(targetObject, "Search") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Open"); if (interacted) { log.info("Interacted with object for clue."); @@ -159,13 +158,13 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - var targetNpc = Rs2Npc.getNpc(clue.getNpcName(clueScrollPlugin)); + var targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearestOnClientThread(); if (targetNpc == null) { log.warn("NPC {} not found.", clue.getNpcName(clueScrollPlugin)); return false; } - boolean interacted = Rs2Npc.interact(targetNpc, "Talk-to"); + boolean interacted = targetNpc.click("Talk-to"); if (interacted) { log.info("Talking to NPC: {}", clue.getNpcName(clueScrollPlugin)); Rs2Dialogue.sleepUntilInDialogue(); @@ -197,10 +196,6 @@ private boolean handleDialogue() { return false; } - private boolean hasArrived(Player player) { - return player.getWorldLocation().equals(location); - } - private boolean isWithinRadius(WorldPoint targetLocation, WorldPoint playerLocation, int radius) { int deltaX = Math.abs(targetLocation.getX() - playerLocation.getX()); int deltaY = Math.abs(targetLocation.getY() - playerLocation.getY()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java index ca2b4b059a..b3403e3968 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java @@ -13,7 +13,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -116,13 +116,13 @@ public void onGameTick(GameTick event) { } private boolean interactWithNpc() { - var npc = Rs2Npc.getNpc(clue.getNpc()); + var npc = Microbot.getRs2NpcCache().query().withId(clue.getNpc()).nearest(); if (npc == null) { log.warn("NPC with ID {} not found at the location.", clue.getNpc()); return false; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log.info("Interacting with NPC for cipher clue."); Rs2Dialogue.sleepUntilInDialogue(); return handleDialogue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java index ba829ffdf2..88d46b2fa9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java @@ -3,7 +3,9 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import java.util.concurrent.CompletableFuture; @@ -65,4 +67,19 @@ protected void completeTask(boolean success) { protected boolean preTaskCheck() { return client != null && client.getLocalPlayer() != null; } + + /** + * v1.0.3 fix: thread-safe player location read. + * + *

Subclasses submit {@code processGameTick} to a background executor, so direct + * {@code client.getLocalPlayer().getWorldLocation()} calls from those code paths throw + * {@code IllegalStateException: must be called on client thread}. Use this helper instead -- + * it hops to the client thread, reads the location, and returns null if the player isn't available. + */ + protected WorldPoint getPlayerLocationSafe() { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (client == null || client.getLocalPlayer() == null) return null; + return client.getLocalPlayer().getWorldLocation(); + }).orElse(null); + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java index 1f9c3a8044..604dafe5e6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java @@ -3,8 +3,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.ItemID; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -14,8 +12,8 @@ import net.runelite.client.plugins.cluescrolls.clues.Enemy; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -95,12 +93,13 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) return; + // v1.0.5 fix: client-thread-safe location read. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (isWithinRadius(location, player.getWorldLocation(), 5)) { + if (isWithinRadius(location, playerLocation, 5)) { log.info("Arrived at coordinate clue location."); state = (enemy != null) ? State.FIGHTING_ENEMY : State.DIGGING; } @@ -134,13 +133,13 @@ private void processGameTick(GameTick event) { } private boolean engageEnemy() { - Rs2NpcModel targetNpc = Rs2Npc.getNpc(enemy.getText()); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(enemy.getText()).nearestOnClientThread(); if (targetNpc == null) { log.warn("Expected enemy not found."); completeTask(false); return false; } - if (Rs2Npc.interact(targetNpc, "Attack")) { + if (targetNpc.click("Attack")) { log.info("Engaging enemy: {}", enemy.getText()); return waitForEnemyDefeat(targetNpc); } @@ -148,13 +147,15 @@ private boolean engageEnemy() { return false; } - private boolean waitForEnemyDefeat(NPC targetNpc) { - return targetNpc.isDead(); // This assumes an enemy tracking system + private boolean waitForEnemyDefeat(Rs2NpcModel targetNpc) { + return targetNpc.getNpc().isDead(); } private boolean prepareToDig() { - Player player = client.getLocalPlayer(); - if (!isWithinRadius(location, player.getWorldLocation(), 1)) { + // v1.0.5 fix: client-thread-safe location read. Called from processGameTick (background executor). + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return false; + if (!isWithinRadius(location, playerLocation, 1)) { log.info("Adjusting position to exact location."); Rs2Walker.walkFastCanvas(location); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java index a92033b2dd..8438c559a9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java @@ -2,8 +2,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -11,12 +9,9 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.CrypticClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; -import net.runelite.client.plugins.microbot.util.models.RS2Item; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.Objects; @@ -88,8 +83,14 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - WorldPoint playerLocation = player.getWorldLocation(); + // v1.0.3 fix: read player location via the client-thread-safe helper instead of + // calling client.getLocalPlayer().getWorldLocation() directly from the background + // executor (which throws IllegalStateException: must be called on client thread). + WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable; will retry next tick"); + return; + } WorldPoint clueLocation = clue.getLocation(clueScrollPlugin); switch (state) { @@ -158,7 +159,7 @@ private void transitionToNextState() { state = State.KILLING_ENEMY; } else if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpc(clueScrollPlugin) != null && Rs2Npc.getNpc(clue.getNpc(clueScrollPlugin)) != null) { + } else if (clue.getNpc(clueScrollPlugin) != null && Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearestOnClientThread() != null) { state = State.INTERACTING_WITH_NPC; } else { state = State.COMPLETED; @@ -167,12 +168,12 @@ private void transitionToNextState() { } private boolean killEnemy() { - Rs2NpcModel enemy = Rs2Npc.getNpc(clue.getEnemy().name()); - if (enemy == null || enemy.getHealthRatio() <= 0) { + Rs2NpcModel enemy = Microbot.getRs2NpcCache().query().withName(clue.getEnemy().name()).nearestOnClientThread(); + if (enemy == null || enemy.getNpc().getHealthRatio() <= 0) { log.info("Enemy {} is defeated. Searching for loot.", clue.getEnemy()); return true; } - if (Rs2Npc.interact(enemy, "Attack")) { + if (enemy.click("Attack")) { log.info("Started attacking enemy: {}", clue.getEnemy()); } else { log.warn("Failed to attack enemy: {}", clue.getEnemy()); @@ -181,11 +182,11 @@ private boolean killEnemy() { } private boolean lootGroundItem() { - RS2Item[] groundItems = Rs2GroundItem.getAll(5); + var groundItems = Microbot.getRs2TileItemCache().query().within(10).toList(); boolean anyLooted = false; - for (RS2Item item : groundItems) { - if (Rs2GroundItem.interact(item)) { + for (var item : groundItems) { + if (item.click("Take")) { log.info("Successfully picked up item: {}", item); anyLooted = true; } else { @@ -197,11 +198,11 @@ private boolean lootGroundItem() { private boolean interactWithObject() { int targetObject = clue.getObjectId(); - if (Rs2GameObject.interact(targetObject, "Search") - || Rs2GameObject.interact(targetObject, "Investigate") - || Rs2GameObject.interact(targetObject, "Examine") - || Rs2GameObject.interact(targetObject, "Look-at") - || Rs2GameObject.interact(targetObject, "Open")) { + if (Microbot.getRs2TileObjectCache().query().interact(targetObject, "Search") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Open")) { log.info("Interacted with required object for the clue."); return true; } @@ -210,12 +211,12 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - Rs2NpcModel targetNpc = Rs2Npc.getNpc(clue.getNpc(clueScrollPlugin)); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearestOnClientThread(); if (targetNpc == null) { log.warn("NPC {} not found at the location.", clue.getNpc(clueScrollPlugin)); return false; } - return Rs2Npc.interact(targetNpc, "Talk-to"); + return targetNpc.click("Talk-to"); } private boolean handleDialogue() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java index 41b2464d3f..b194307fc2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java @@ -13,8 +13,8 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -131,7 +131,15 @@ private void processGameTick(GameTick event) { private void handleWalkingToLocation() { WorldPoint location = clue.getLocation(clueScrollPlugin); - if (client.getLocalPlayer().getWorldLocation().equals(location)) { + // v1.0.3 fix: read player location via the client-thread-safe helper instead of + // calling client.getLocalPlayer().getWorldLocation() directly from the background + // executor (which throws IllegalStateException: must be called on client thread). + WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable; will retry next tick"); + return; + } + if (playerLocation.equals(location)) { log.info("Arrived at Emote Clue location."); state = State.PERFORMING_EMOTES; } else { @@ -177,7 +185,7 @@ private void performEmote(String emoteName) { } private void interactWithUri() { - Rs2Npc.interact("Uri", "Talk-to"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Uri").interact("Talk-to")); log.info("Interacted with Uri."); } @@ -186,7 +194,7 @@ public void onNpcSpawned(NpcSpawned event) { NPC npc = event.getNpc(); if (npc.getName() != null) { if (state == State.WAITING_FOR_ENEMY_SPAWN && npc.getName().equalsIgnoreCase(DOUBLE_AGENT_NAME)) { - doubleAgent = Rs2Npc.getNpcByIndex(npc.getIndex()); + doubleAgent = Microbot.getRs2NpcCache().query().where(n -> n.getIndex() == npc.getIndex()).nearest(); log.info("Double agent spawned."); state = State.FIGHTING_ENEMY; attackDoubleAgent(); @@ -196,7 +204,7 @@ public void onNpcSpawned(NpcSpawned event) { private void attackDoubleAgent() { if (doubleAgent != null) { - Rs2Npc.interact(doubleAgent, "Attack"); + doubleAgent.click("Attack"); log.info("Attacking Double Agent."); } else { log.warn("Double Agent NPC is null or missing."); @@ -208,7 +216,7 @@ private void attackDoubleAgent() { public void onInteractingChanged(InteractingChanged event) { if (state == State.FIGHTING_ENEMY && event.getSource() == client.getLocalPlayer() && event.getTarget() == null) { - if (doubleAgent == null || Rs2Npc.getHealth(doubleAgent) <= 0) { + if (doubleAgent == null || doubleAgent.getNpc().getHealthRatio() <= 0) { log.info("Double agent defeated."); enemyDefeated = true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java index 2a1af76fa5..227177e01b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java @@ -10,7 +10,7 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.FaloTheBardClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -110,7 +110,8 @@ private boolean isPlayerAtLocation() { private boolean interactWithNpc() { log.info("Interacting with Falo the Bard NPC."); - return Rs2Npc.interact("Falo the Bard", "Talk-to"); + var falo = Microbot.getRs2NpcCache().query().withName("Falo the Bard").nearestOnClientThread(); + return falo != null && falo.click("Talk-to"); } private boolean confirmClueCompletion() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java index f77ee60d01..8145e0f2ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java @@ -11,7 +11,7 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.MapClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -112,11 +112,11 @@ public void onGameTick(GameTick event) { private boolean interactWithObject() { if (objectId == -1) return false; - boolean interactionSuccessful = Rs2GameObject.interact(objectId, "Search") - || Rs2GameObject.interact(objectId, "Investigate") - || Rs2GameObject.interact(objectId, "Examine") - || Rs2GameObject.interact(objectId, "Look-at") - || Rs2GameObject.interact(objectId, "Open"); + boolean interactionSuccessful = Microbot.getRs2TileObjectCache().query().interact(objectId, "Search") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Open"); if (interactionSuccessful) { log.info("Interacted with required object for the clue."); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java index d18beacf68..a0882a1f78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java @@ -2,8 +2,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -11,8 +9,8 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.MusicClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -85,12 +83,13 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) return; + // v1.0.5 fix: client-thread-safe location read. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (isWithinRadius(location, player.getWorldLocation(), 5)) { + if (isWithinRadius(location, playerLocation, 5)) { log.info("Arrived at music clue location."); state = State.PLAYING_SONG; } @@ -143,13 +142,13 @@ private boolean playSong() { } private boolean interactWithNpc() { - Rs2NpcModel npc = Rs2Npc.getNpc(npcName); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withName(npcName).nearestOnClientThread(); if (npc == null) { log.warn("NPC {} not found near the clue location.", npcName); return false; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log.info("Interacted with NPC: {}", npcName); return true; } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java index 4f5f4ce4b0..9231599ee4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java @@ -90,7 +90,18 @@ private void fetchMissingItemsFromBank(List missingItems) { private void walkToBank() { if (Rs2Bank.isOpen()) { fetchNextItem(); - } else if (client.getLocalPlayer().getWorldLocation().distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) > 10) { + return; + } + // v1.0.4 fix: read player location via the client-thread-safe helper. walkToBank() may + // be called from off-client-thread code paths (executeTask flow). Direct + // client.getLocalPlayer().getWorldLocation() throws IllegalStateException, which + // cascaded into "Failed to open the bank" + walker deadlock. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable in walkToBank; will retry next tick"); + return; + } + if (playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) > 10) { Rs2Bank.walkToBankAndUseBank(); } else { openBank(); @@ -149,7 +160,13 @@ private void fetchNextItem() { public void onGameTick(GameTick event) { if (Rs2Bank.isOpen() && currentRequirement == null) { fetchNextItem(); - } else if (!Rs2Bank.isOpen() && client.getLocalPlayer().getWorldLocation().distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) <= 5) { + return; + } + // v1.0.4 fix: defensive client-thread-safe read. onGameTick is @Subscribe-annotated so + // it normally runs on the client thread, but using the helper is harmless and consistent. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; + if (!Rs2Bank.isOpen() && playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) <= 5) { openBank(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java new file mode 100644 index 0000000000..08739454c8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java @@ -0,0 +1,47 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.annotation.Nullable; +import java.util.List; + +final class ColosseumPrayerArbiter { + + private ColosseumPrayerArbiter() { + } + + /** + * If multiple prayers are demanded the same tick, the threat with the lowest {@link ColosseumPrayerThreat#arbiterRank()} + * wins. Javelin + serpent shaman pairs are resolved in {@link net.runelite.client.plugins.microbot.colosseumprayer.ColosseumPrayerScript} + * by tick alternation, not here. If tied on rank, earliest entry in {@code demands} wins. + */ + @Nullable + static Rs2PrayerEnum resolve(List demands) { + if (demands.isEmpty()) { + return null; + } + ColosseumPrayerDemand best = null; + for (ColosseumPrayerDemand candidate : demands) { + if (best == null) { + best = candidate; + continue; + } + if (beats(candidate, best)) { + best = candidate; + } + } + if (best == null) { + return null; + } + return best.protection(); + } + + private static boolean beats(ColosseumPrayerDemand candidate, ColosseumPrayerDemand incumbent) { + int c = candidate.threat().arbiterRank(); + int i = incumbent.threat().arbiterRank(); + if (c < i) { + return true; + } + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java new file mode 100644 index 0000000000..6ef37c9f5a --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java @@ -0,0 +1,32 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(ColosseumPrayerConfig.configGroup) +@ConfigInformation("Prayer helper for Fortis Colosseum waves 1–11 (Manticore, Javelin Colossus, Serpent Shaman, Jaguar warrior, Shockwave Colossus). Wave 12 not included.") +public interface ColosseumPrayerConfig extends Config { + String configGroup = "micro-fortiscolosseum-prayer"; + + @ConfigItem( + keyName = "helperEnabled", + name = "Enable helper", + description = "Turn the scheduled prayer arbiter loop on.", + position = 0 + ) + default boolean helperEnabled() { + return true; + } + + @ConfigItem( + keyName = "debugLogSignals", + name = "Debug: log arbitration", + description = "Log winning prayer resolution each tick cycle (verbose).", + position = 1 + ) + default boolean debugLogSignals() { + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java new file mode 100644 index 0000000000..371f288c3e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +/** + * Resolved demand for one protection overhead from a single threat class. + */ +final class ColosseumPrayerDemand { + private final ColosseumPrayerThreat threat; + private final Rs2PrayerEnum protection; + + ColosseumPrayerDemand(ColosseumPrayerThreat threat, Rs2PrayerEnum protection) { + this.threat = threat; + this.protection = protection; + } + + ColosseumPrayerThreat threat() { + return threat; + } + + Rs2PrayerEnum protection() { + return protection; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java new file mode 100644 index 0000000000..d9178ccb40 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java @@ -0,0 +1,47 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; +import java.awt.*; + +@PluginDescriptor( + name = PluginConstants.DEFAULT_PREFIX + "Fortis Colosseum Prayer", + description = "Prayer helper for Fortis Colosseum waves 1–11: manticore, javelin colossus, serpent shaman, jaguar warrior, shockwave colossus.", + tags = {"colosseum", "fortis", "prayer", "microbot"}, + authors = {"Microbot Hub"}, + version = ColosseumPrayerPlugin.version, + minClientVersion = "1.9.8.8", + cardUrl = "https://chsami.github.io/Microbot-Hub/ColosseumPrayerPlugin/assets/card.png", + iconUrl = "https://chsami.github.io/Microbot-Hub/ColosseumPrayerPlugin/assets/icon.png", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class ColosseumPrayerPlugin extends Plugin { + + static final String version = "1.0.3"; + + @Provides + ColosseumPrayerConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(ColosseumPrayerConfig.class); + } + + @Inject + private ColosseumPrayerScript colosseumPrayerScript; + + @Override + protected void startUp() throws AWTException { + colosseumPrayerScript.register(); + } + + @Override + protected void shutDown() { + colosseumPrayerScript.unregister(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java new file mode 100644 index 0000000000..e40bb21e66 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java @@ -0,0 +1,734 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ActorSpotAnim; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.Projectile; +import net.runelite.api.WorldView; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.ProjectileMoved; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Arbiter-driven threats on {@link GameTick}; manticore triple uses {@link ProjectileMoved} signals. + */ +@Slf4j +@Singleton +public class ColosseumPrayerScript { + + private static final int TILE_RANGE_MELEE_THREAT = 3; + private static final int TILE_RANGE_RANGED_MAGING = 18; + + private final EventBus eventBus; + private final ColosseumPrayerConfig config; + private final Map manticoreStates = new HashMap<>(); + private final Map seenManticoreLaunches = new HashMap<>(); + private boolean registered; + /** Suppress duplicate toggles within the same game tick during manticore volleys (styles are one tick apart). */ + private int lastManticoreSwitchGameTick = -1; + private int lastManticoreDebugTick = -1; + private int lastAnyPrayerSwitchTick = -1; + private Rs2PrayerEnum lastAnyPrayerSwitchPrayer; + + @Inject + ColosseumPrayerScript(EventBus eventBus, ColosseumPrayerConfig config) { + this.eventBus = eventBus; + this.config = config; + } + + void register() { + if (registered) { + return; + } + eventBus.register(this); + registered = true; + } + + void unregister() { + if (!registered) { + return; + } + eventBus.unregister(this); + registered = false; + lastManticoreSwitchGameTick = -1; + lastManticoreDebugTick = -1; + lastAnyPrayerSwitchTick = -1; + lastAnyPrayerSwitchPrayer = null; + manticoreStates.clear(); + seenManticoreLaunches.clear(); + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + if (!config.helperEnabled()) { + return; + } + if (!Microbot.isLoggedIn()) { + return; + } + Client client = Microbot.getClient(); + if (client == null) { + return; + } + Projectile projectile = event.getProjectile(); + if (projectile == null) { + return; + } + Rs2PrayerEnum incoming = ManticoreProjectilePrayers.prayerForProjectileId(projectile.getId()); + if (incoming == null) { + return; + } + if (manticoreStates.isEmpty()) { + return; + } + if (projectile.getRemainingCycles() <= 0) { + return; + } + int tickNow = client.getTickCount(); + WorldPoint targetPoint = projectile.getTargetPoint(); + long launchKey = buildManticoreLaunchKey(projectile.getId(), projectile.getStartCycle(), targetPoint); + Integer seenTick = seenManticoreLaunches.get(launchKey); + if (seenTick != null) { + return; + } + seenManticoreLaunches.put(launchKey, tickNow); + ManticoreCycleState state = selectLaunchState(tickNow); + if (state == null) { + return; + } + state.markSeen(tickNow); + state.onProjectileLaunch(incoming, tickNow); + Rs2PrayerEnum nextPrayer = state.currentDesiredPrayer(); + if (nextPrayer == null) { + nextPrayer = incoming; + } + switchManticoreProtection(client, nextPrayer); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Manticore idx={} projectile={} launched={} next={}", + state.npcIndex, projectile.getId(), incoming, nextPrayer); + } + } + + @Subscribe + public void onGameTick(GameTick tick) { + if (!Microbot.isLoggedIn()) { + return; + } + if (!config.helperEnabled()) { + return; + } + Client client = Microbot.getClient(); + if (client == null) { + return; + } + refreshManticoreStates(client); + Rs2PrayerEnum manticorePrayer = resolveManticorePrayer(client); + if (manticorePrayer != null) { + switchManticoreProtection(client, manticorePrayer); + if (config.debugLogSignals()) { + int tickNow = client.getTickCount(); + if (tickNow != lastManticoreDebugTick) { + log.info("[ColosseumPrayer] MantiTick tick={} choose={} states={}", + tickNow, manticorePrayer, formatManticoreStates()); + lastManticoreDebugTick = tickNow; + } + } + return; + } + if (!hasTrackedThreatNpcInScene()) { + return; + } + List demands = gatherDemands(); + Player localPlayer = client.getLocalPlayer(); + if (shouldFlickJavelinWithSerpentShaman(demands, localPlayer)) { + Client clientForFlick = Microbot.getClient(); + if (clientForFlick != null) { + applyJavelinSerpentShamanFlickPrayer(clientForFlick); + } + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Javelin + Serpent Shaman flick tick={}", + Microbot.getClient() != null ? Microbot.getClient().getTickCount() : -1); + } + return; + } + if (shouldForceSerpentMagePrayer(demands, localPlayer)) { + trySwitchPrayer(client, Rs2PrayerEnum.PROTECT_MAGIC); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] LOS override: shaman visible, javelin blocked -> PROTECT_MAGIC"); + } + return; + } + Rs2PrayerEnum resolved = ColosseumPrayerArbiter.resolve(demands); + if (resolved == null) { + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Tick: tracked threats nearby but no active demand (interact/range)"); + } + return; + } + trySwitchPrayer(client, resolved); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] demands={} resolved={}", demands.size(), resolved); + } + } + + private void refreshManticoreStates(Client client) { + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) { + manticoreStates.clear(); + seenManticoreLaunches.clear(); + return; + } + int tickNow = client.getTickCount(); + seenManticoreLaunches.entrySet().removeIf(e -> tickNow - e.getValue() > 8); + Set liveIndexes = new HashSet<>(); + for (NPC npc : worldView.npcs()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + if (!isManticoreName(npc.getName())) { + continue; + } + int npcIndex = npc.getIndex(); + liveIndexes.add(npcIndex); + ManticoreCycleState state = manticoreStates.computeIfAbsent(npcIndex, ManticoreCycleState::new); + state.markSeen(tickNow); + List visualOrder = extractManticoreOrbOrder(npc); + if (visualOrder.size() == 3) { + state.updateVisualOrder(visualOrder, tickNow); + } + Player local = client.getLocalPlayer(); + if (local != null) { + WorldPoint npcWp = npc.getWorldLocation(); + WorldPoint playerWp = local.getWorldLocation(); + if (npcWp != null && playerWp != null) { + state.distanceToPlayer = playerWp.distanceTo(npcWp); + } + WorldArea npcArea = npc.getWorldArea(); + WorldArea playerArea = local.getWorldArea(); + if (npcArea != null && playerArea != null) { + state.hasLineOfSight = playerArea.hasLineOfSightTo(worldView, npcArea); + } else { + state.hasLineOfSight = false; + } + } else { + state.hasLineOfSight = false; + } + } + manticoreStates.entrySet().removeIf(e -> { + ManticoreCycleState state = e.getValue(); + if (!liveIndexes.contains(e.getKey())) { + return true; + } + return state.isStale(tickNow); + }); + } + + private Rs2PrayerEnum resolveManticorePrayer(Client client) { + if (manticoreStates.isEmpty()) { + return null; + } + int tickNow = client.getTickCount(); + ManticoreCycleState best = null; + for (ManticoreCycleState state : manticoreStates.values()) { + if (!state.hasLineOfSight) { + continue; + } + Rs2PrayerEnum desired = state.currentDesiredPrayer(); + if (desired == null) { + continue; + } + if (best == null) { + best = state; + continue; + } + if (state.beats(best, tickNow)) { + best = state; + } + } + if (best == null) { + return null; + } + return best.currentDesiredPrayer(); + } + + private static List extractManticoreOrbOrder(NPC npc) { + List visuals = new ArrayList<>(); + for (ActorSpotAnim spotAnim : npc.getSpotAnims()) { + if (spotAnim == null) { + continue; + } + Rs2PrayerEnum prayer = ManticoreProjectilePrayers.prayerForProjectileId(spotAnim.getId()); + if (prayer == null) { + continue; + } + visuals.add(new ManticoreOrbVisual(prayer, spotAnim.getHeight(), spotAnim.getStartCycle())); + } + visuals.sort(Comparator + .comparingInt((ManticoreOrbVisual v) -> v.height) + .thenComparingInt(v -> v.startCycle)); + List order = new ArrayList<>(); + for (ManticoreOrbVisual visual : visuals) { + order.add(visual.prayer); + } + return order; + } + + private static boolean isManticoreName(String name) { + if (name == null) { + return false; + } + return name.toLowerCase().contains("manticore"); + } + + private static long buildManticoreLaunchKey(int projectileId, int startCycle, WorldPoint targetPoint) { + long key = 17; + key = key * 31 + projectileId; + key = key * 31 + startCycle; + if (targetPoint != null) { + key = key * 31 + targetPoint.getX(); + key = key * 31 + targetPoint.getY(); + key = key * 31 + targetPoint.getPlane(); + } + return key; + } + + private ManticoreCycleState selectLaunchState(int tickNow) { + ManticoreCycleState best = null; + for (ManticoreCycleState candidate : manticoreStates.values()) { + if (!candidate.hasLineOfSight) { + continue; + } + if (candidate.currentDesiredPrayer() == null) { + continue; + } + if (best == null || candidate.beats(best, tickNow)) { + best = candidate; + } + } + return best; + } + + private String formatManticoreStates() { + if (manticoreStates.isEmpty()) { + return "none"; + } + List sorted = new ArrayList<>(manticoreStates.values()); + sorted.sort(Comparator.comparingInt(s -> s.npcIndex)); + List chunks = new ArrayList<>(); + for (ManticoreCycleState state : sorted) { + chunks.add(state.describe()); + } + return String.join(" | ", chunks); + } + + private void switchManticoreProtection(Client client, Rs2PrayerEnum prayer) { + int tickNow = client.getTickCount(); + if (Rs2Prayer.isPrayerActive(prayer)) { + return; + } + if (tickNow == lastManticoreSwitchGameTick) { + return; + } + if (!trySwitchPrayer(client, prayer)) { + return; + } + lastManticoreSwitchGameTick = tickNow; + } + + /** + * Alternate Protect Missiles and Protect Magic when both ranged javelin autos and serpent shamans threaten. + * Skipped while a jaguar warrior also demands melee (fallback to arbiter). + */ + private boolean shouldFlickJavelinWithSerpentShaman(List demands, Player player) { + if (!demandHasThreat(demands, ColosseumPrayerThreat.JAVELIN_COLOSSUS)) { + return false; + } + if (!demandHasThreat(demands, ColosseumPrayerThreat.SERPENT_SHAMAN)) { + return false; + } + if (demandHasThreat(demands, ColosseumPrayerThreat.JAGUAR_WARRIOR)) { + return false; + } + if (!hasLineOfSightThreat(ThreatKind.JAVELIN_COLOSSUS, player)) { + return false; + } + if (!hasLineOfSightThreat(ThreatKind.SERPENT_SHAMAN, player)) { + return false; + } + return true; + } + + private boolean shouldForceSerpentMagePrayer(List demands, Player player) { + if (!demandHasThreat(demands, ColosseumPrayerThreat.JAVELIN_COLOSSUS)) { + return false; + } + if (!demandHasThreat(demands, ColosseumPrayerThreat.SERPENT_SHAMAN)) { + return false; + } + boolean shamanLos = hasLineOfSightThreat(ThreatKind.SERPENT_SHAMAN, player); + boolean javelinLos = hasLineOfSightThreat(ThreatKind.JAVELIN_COLOSSUS, player); + if (shamanLos && !javelinLos) { + return true; + } + return false; + } + + private static boolean demandHasThreat(List demands, ColosseumPrayerThreat threat) { + for (ColosseumPrayerDemand d : demands) { + if (d.threat() == threat) { + return true; + } + } + return false; + } + + /** + * Even game ticks: Protect Missiles (javelin autos). Odd ticks: Protect Magic (serpent shaman). + */ + private void applyJavelinSerpentShamanFlickPrayer(Client client) { + int tick = client.getTickCount(); + Rs2PrayerEnum want; + if (tick % 2 == 0) { + want = Rs2PrayerEnum.PROTECT_RANGE; + } else { + want = Rs2PrayerEnum.PROTECT_MAGIC; + } + if (Rs2Prayer.isPrayerActive(want)) { + return; + } + trySwitchPrayer(client, want); + } + + private boolean trySwitchPrayer(Client client, Rs2PrayerEnum prayer) { + if (client == null || prayer == null) { + return false; + } + int tick = client.getTickCount(); + if (Rs2Prayer.isPrayerActive(prayer)) { + return false; + } + if (tick == lastAnyPrayerSwitchTick) { + if (prayer == lastAnyPrayerSwitchPrayer) { + return false; + } + return false; + } + boolean ok = Rs2Prayer.toggle(prayer, true, false); + if (ok) { + lastAnyPrayerSwitchTick = tick; + lastAnyPrayerSwitchPrayer = prayer; + } + return ok; + } + + private boolean hasLineOfSightThreat(ThreatKind kind, Player player) { + if (player == null) { + return false; + } + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) { + return false; + } + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null || npc.isDead()) { + continue; + } + if (classifyThreat(npc) != kind) { + continue; + } + WorldArea npcArea = npc.getWorldArea(); + WorldArea playerArea = player.getWorldArea(); + if (npcArea == null || playerArea == null) { + continue; + } + if (playerArea.hasLineOfSightTo(worldView, npcArea)) { + return true; + } + } + return false; + } + + private boolean hasTrackedThreatNpcInScene() { + if (Microbot.getClient() == null) { + return false; + } + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + ThreatKind kind = classifyThreat(npc); + if (kind != null) { + return true; + } + } + return false; + } + + List gatherDemands() { + Player player = Microbot.getClient() != null ? Microbot.getClient().getLocalPlayer() : null; + List demands = new ArrayList<>(); + if (player == null) { + return demands; + } + WorldPoint playerWp = Rs2Player.getWorldLocation(); + boolean instanced = isInstanced(); + + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + ThreatKind kind = classifyThreat(npc); + if (kind == null) { + continue; + } + if (kind == ThreatKind.MANTICORE) { + continue; + } + WorldPoint npcWp = npcWorldLocation(npc, instanced); + if (npcWp == null) { + continue; + } + int dist = playerWp.distanceTo(npcWp); + + if (kind == ThreatKind.JAVELIN_COLOSSUS) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.JAVELIN_COLOSSUS, + Rs2PrayerEnum.PROTECT_RANGE)); + } + continue; + } + if (kind == ThreatKind.SERPENT_SHAMAN) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.SERPENT_SHAMAN, + Rs2PrayerEnum.PROTECT_MAGIC)); + } + continue; + } + if (kind == ThreatKind.SHOCKWAVE_COLOSSUS) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.SHOCKWAVE_COLOSSUS, + Rs2PrayerEnum.PROTECT_MAGIC)); + } + continue; + } + if (kind == ThreatKind.JAGUAR_WARRIOR) { + if (threatEligible(player, npc, dist, TILE_RANGE_MELEE_THREAT)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.JAGUAR_WARRIOR, + Rs2PrayerEnum.PROTECT_MELEE)); + } + } + } + return demands; + } + + private enum ThreatKind { + MANTICORE, + JAVELIN_COLOSSUS, + SERPENT_SHAMAN, + SHOCKWAVE_COLOSSUS, + JAGUAR_WARRIOR + } + + private static ThreatKind classifyThreat(Rs2NpcModel npc) { + if (npc == null) { + return null; + } + String name = npc.getName(); + if (name == null) { + return null; + } + String normalized = name.toLowerCase(); + if (normalized.contains("manticore")) { + return ThreatKind.MANTICORE; + } + if (normalized.contains("serpent")) { + if (normalized.contains("shaman")) { + return ThreatKind.SERPENT_SHAMAN; + } + } + if (normalized.contains("shockwave")) { + return ThreatKind.SHOCKWAVE_COLOSSUS; + } + if (normalized.contains("colossus")) { + return ThreatKind.JAVELIN_COLOSSUS; + } + if (normalized.contains("javelin")) { + return ThreatKind.JAVELIN_COLOSSUS; + } + if (normalized.contains("jaguar")) { + return ThreatKind.JAGUAR_WARRIOR; + } + return null; + } + + private static boolean threatEligible(Player player, Rs2NpcModel npc, int chebyshevTiles, int maxRangeTiles) { + if (npc.isInteractingWithPlayer()) { + return true; + } + Actor interacting = npc.getInteracting(); + if (Objects.equals(interacting, player)) { + return true; + } + return chebyshevTiles <= maxRangeTiles; + } + + private static boolean isInstanced() { + Client c = Microbot.getClient(); + if (c == null) { + return false; + } + WorldView wv = c.getTopLevelWorldView(); + if (wv == null) { + return false; + } + return wv.getScene().isInstance(); + } + + private static WorldPoint npcWorldLocation(Rs2NpcModel npc, boolean instanced) { + if (npc == null) { + return null; + } + if (instanced) { + LocalPoint lp = npc.getLocalLocation(); + Client c = Microbot.getClient(); + if (lp == null) { + return null; + } + if (c == null) { + return null; + } + WorldPoint wp = WorldPoint.fromLocalInstance(c, lp); + return wp; + } + return npc.getWorldLocation(); + } + + private static final class ManticoreOrbVisual { + private final Rs2PrayerEnum prayer; + private final int height; + private final int startCycle; + + private ManticoreOrbVisual(Rs2PrayerEnum prayer, int height, int startCycle) { + this.prayer = prayer; + this.height = height; + this.startCycle = startCycle; + } + } + + private static final class ManticoreCycleState { + private final int npcIndex; + private final List orbOrder = new ArrayList<>(3); + private int nextPrayerIndex; + private int lastProjectileTick = -1000; + private int lastSeenTick = -1000; + private int distanceToPlayer = 99; + private boolean hasLineOfSight; + + private ManticoreCycleState(int npcIndex) { + this.npcIndex = npcIndex; + } + + private void updateVisualOrder(List order, int tickNow) { + if (!orbOrder.equals(order)) { + orbOrder.clear(); + orbOrder.addAll(order); + nextPrayerIndex = 0; + } + lastSeenTick = tickNow; + } + + private void onProjectileLaunch(Rs2PrayerEnum launchedPrayer, int tickNow) { + lastProjectileTick = tickNow; + lastSeenTick = tickNow; + if (orbOrder.isEmpty()) { + return; + } + int launchedIndex = orbOrder.indexOf(launchedPrayer); + if (launchedIndex >= 0) { + nextPrayerIndex = (launchedIndex + 1) % orbOrder.size(); + return; + } + nextPrayerIndex = (nextPrayerIndex + 1) % orbOrder.size(); + } + + private Rs2PrayerEnum currentDesiredPrayer() { + if (orbOrder.isEmpty()) { + return null; + } + if (nextPrayerIndex < 0 || nextPrayerIndex >= orbOrder.size()) { + nextPrayerIndex = 0; + } + return orbOrder.get(nextPrayerIndex); + } + + private void markSeen(int tickNow) { + lastSeenTick = tickNow; + } + + private boolean isStale(int tickNow) { + return tickNow - lastSeenTick > 10; + } + + private boolean beats(ManticoreCycleState other, int tickNow) { + boolean thisRecentlyLaunched = tickNow - this.lastProjectileTick <= 2; + boolean otherRecentlyLaunched = tickNow - other.lastProjectileTick <= 2; + if (thisRecentlyLaunched != otherRecentlyLaunched) { + return thisRecentlyLaunched; + } + if (this.lastProjectileTick != other.lastProjectileTick) { + return this.lastProjectileTick > other.lastProjectileTick; + } + if (this.distanceToPlayer != other.distanceToPlayer) { + return this.distanceToPlayer < other.distanceToPlayer; + } + return this.npcIndex < other.npcIndex; + } + + private String describe() { + return "idx=" + npcIndex + + ",next=" + currentDesiredPrayer() + + ",order=" + orbOrder + + ",dist=" + distanceToPlayer + + ",los=" + hasLineOfSight + + ",lastProjTick=" + lastProjectileTick; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java new file mode 100644 index 0000000000..82d4e01baa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +/** + * Threat sources for arbitration. Lower rank number wins same-tick conflicts. + * Baseline priority: manticore → javelin → serpent shaman → jaguar → shockwave. + * When javelin and serpent shaman both demand different prayers, the script alternates per tick instead of arbiter ranking. + */ +enum ColosseumPrayerThreat { + MANTICORE(1), + JAVELIN_COLOSSUS(2), + SERPENT_SHAMAN(3), + JAGUAR_WARRIOR(4), + SHOCKWAVE_COLOSSUS(5); + + private final int arbiterRank; + + ColosseumPrayerThreat(int arbiterRank) { + this.arbiterRank = arbiterRank; + } + + int arbiterRank() { + return arbiterRank; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java new file mode 100644 index 0000000000..02b2425947 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java @@ -0,0 +1,29 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.api.gameval.SpotanimID; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.annotation.Nullable; + +/** + * Maps Fortis Colosseum manticore orb projectiles ({@link SpotanimID}) to protection prayers. + */ +final class ManticoreProjectilePrayers { + + private ManticoreProjectilePrayers() { + } + + @Nullable + static Rs2PrayerEnum prayerForProjectileId(int projectileId) { + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_MAGIC_01) { + return Rs2PrayerEnum.PROTECT_MAGIC; + } + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_RANGED_01) { + return Rs2PrayerEnum.PROTECT_RANGE; + } + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_MELEE_01) { + return Rs2PrayerEnum.PROTECT_MELEE; + } + return null; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java index 2bb17e9285..d8a3e05141 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java @@ -12,6 +12,35 @@ @ConfigGroup("combathotkeys") public interface CombatHotkeysConfig extends Config { + // ========================================================================= + // DEBUG SECTION — appears at the top (position 0) so it's always visible + // ========================================================================= + + @ConfigSection( + name = "Debug", + description = "Enable the on-screen debug panel and verbose logging to the RuneLite log", + position = 0, + closedByDefault = true + ) + String debugSection = "debugSection"; + + @ConfigItem( + keyName = "debugMode", + name = "Show debug panel", + description = "Renders a debug overlay showing the last key received, last action dispatched, " + + "submitted/succeeded/failed counters, and any error. Also enables TRACE-level logging " + + "from the plugin — check the RuneLite log (Help → Open logs folder) for full detail.", + position = 0, + section = debugSection + ) + default boolean debugMode() { + return false; + } + + // ========================================================================= + // OFFENSIVE SECTION + // ========================================================================= + @ConfigSection( name = "Offensive Hotkeys", description = "Offensive Prayer and attack hotkeys", @@ -86,6 +115,10 @@ public interface CombatHotkeysConfig extends Config { ) default Keybind specialAttackKey() { return Keybind.NOT_SET; } + // ========================================================================= + // DEFENSIVE PRAYERS SECTION + // ========================================================================= + @ConfigSection( name = "Defensive Prayers", description = "Defensive Prayer hotkeys", @@ -129,6 +162,10 @@ default Keybind protectFromMelee() return Keybind.NOT_SET; } + // ========================================================================= + // FOOD & POTIONS SECTION + // ========================================================================= + @ConfigSection( name = "Food & Potions", description = "Food & Potions", @@ -172,6 +209,10 @@ default Keybind drinkPrayerPotion() return Keybind.NOT_SET; } + // ========================================================================= + // GEAR SETUPS SECTION + // ========================================================================= + @ConfigSection( name = "Gear setups", description = "Gear setups", @@ -179,6 +220,17 @@ default Keybind drinkPrayerPotion() ) String gearSetup = "gearSetup"; + @ConfigItem( + keyName = "maxDelay", + name = "Max Equip Delay (ms)", + description = "Maximum random delay (in milliseconds) between equipping items", + position = 0, + section = gearSetup + ) + default int maxDelay() { + return 500; + } + @ConfigItem( keyName = "Hotkey for gear 1", name = "Hotkey for gear 1", @@ -286,13 +338,46 @@ default Keybind gear5() { keyName = "Gear IDs 5", name = "Gear IDs 5", description = "List of Gear IDs comma separated", - position = 2, + position = 10, section = gearSetup ) default String gearList5() { return ""; } + // ========================================================================= + // MULTISKILLING SECTION + // ========================================================================= + + @ConfigSection( + name = "Multiskilling", + description = "Multiskilling hotkeys", + position = 5 + ) + String multiskillingSection = "multiskillingSection"; + + @ConfigItem( + keyName = "Alchemy", + name = "Alchemy", + description = "Keybind to perform alchemy spell", + position = 0, + section = multiskillingSection + ) + default Keybind highAlchemyKey() { return Keybind.NOT_SET; } + + @ConfigItem( + keyName = "itemToAlch", + name = "Item to Alch", + description = "Enter exact item name to alch (e.g. 'Gold Bar')", + position = 1, + section = multiskillingSection + ) + default String itemToAlch() { return null; } + + // ========================================================================= + // DANCE SECTION + // ========================================================================= + @ConfigItem( keyName = "dance boolean", name = "dance", @@ -303,7 +388,6 @@ default boolean yesDance() { return false; } - // config item for a keybind to enable the dance feature @ConfigItem( keyName = "dance", name = "Dance", @@ -314,7 +398,6 @@ default Keybind dance() { return Keybind.NOT_SET; } - // hidden config for worldpoint called tile1 @ConfigItem( keyName = "tile1", name = "", @@ -324,6 +407,7 @@ default Keybind dance() { default WorldPoint tile1() { return null; } + @ConfigItem( keyName = "tile2", name = "", @@ -334,60 +418,22 @@ default WorldPoint tile2() { return null; } - @ConfigItem( - keyName = "maxDelay", - name = "Max Equip Delay (ms)", - description = "Maximum random delay (in milliseconds) between equipping items", - position = 0, - section = gearSetup - ) - default int maxDelay() { - return 500; // default max delay of 500ms - } - - @ConfigSection( - name = "Multiskilling", - description = "Multiskilling hotkeys", - position = 5 - ) - String multiskillingSection = "multiskillingSection"; + // ========================================================================= + // ENUMS + // ========================================================================= - @ConfigItem( - keyName = "Alchemy", - name = "Alchemy", - description = "Keybind to perform alchemy spell", - position = 0, - section = multiskillingSection - ) - default Keybind highAlchemyKey() { return Keybind.NOT_SET; } - - @ConfigItem( - keyName = "itemToAlch", - name = "Item to Alch", - description = "Enter exact item name to alch (e.g. 'Gold Bar')", - position = 1, - section = multiskillingSection - ) - default String itemToAlch() { return null; } - - public enum MeleePrayerOption { + enum MeleePrayerOption { SUPERHUMAN_STRENGTH(Rs2PrayerEnum.SUPERHUMAN_STRENGTH), ULTIMATE_STRENGTH(Rs2PrayerEnum.ULTIMATE_STRENGTH), CHIVALRY(Rs2PrayerEnum.CHIVALRY), PIETY(Rs2PrayerEnum.PIETY); private final Rs2PrayerEnum prayer; - - MeleePrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + MeleePrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } - public enum RangedPrayerOption { + enum RangedPrayerOption { SHARP_EYE(Rs2PrayerEnum.SHARP_EYE), HAWK_EYE(Rs2PrayerEnum.HAWK_EYE), EAGLE_EYE(Rs2PrayerEnum.EAGLE_EYE), @@ -395,30 +441,18 @@ public enum RangedPrayerOption { RIGOUR(Rs2PrayerEnum.RIGOUR); private final Rs2PrayerEnum prayer; - - RangedPrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + RangedPrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } - public enum MagicPrayerOption { + enum MagicPrayerOption { MYSTIC_WILL(Rs2PrayerEnum.MYSTIC_WILL), MYSTIC_LORE(Rs2PrayerEnum.MYSTIC_LORE), MYSTIC_MIGHT(Rs2PrayerEnum.MYSTIC_MIGHT), AUGURY(Rs2PrayerEnum.AUGURY); private final Rs2PrayerEnum prayer; - - MagicPrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + MagicPrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java index b8b9fea28b..7e425e610d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java @@ -15,58 +15,234 @@ import javax.annotation.Nullable; import javax.inject.Inject; import java.awt.*; +import java.time.Instant; public class CombatHotkeysOverlay extends Overlay { - CombatHotkeysConfig config; + private static final int PANEL_X = 10; + private static final int PANEL_Y = 10; + private static final int PANEL_W = 310; + private static final int ROW_H = 16; + private static final int PAD = 6; + + private static final Color BG_COLOR = new Color(0, 0, 0, 180); + private static final Color BORDER_COLOR = new Color(255, 165, 0, 220); // orange + private static final Color LABEL_COLOR = new Color(180, 180, 180); + private static final Color VALUE_COLOR = Color.WHITE; + private static final Color OK_COLOR = new Color(80, 220, 80); + private static final Color ERR_COLOR = new Color(255, 80, 80); + private static final Color TITLE_COLOR = new Color(255, 165, 0); + + private final CombatHotkeysPlugin plugin; + private final CombatHotkeysConfig config; + @Inject CombatHotkeysOverlay(CombatHotkeysPlugin plugin, CombatHotkeysConfig config) { - super(plugin); + this.plugin = plugin; this.config = config; setPosition(OverlayPosition.DYNAMIC); setNaughty(); setLayer(OverlayLayer.ABOVE_SCENE); } + @Override public Dimension render(Graphics2D graphics) { try { - if(config.yesDance()) { + // ---------------------------------------------------------------- + // Dance tiles (original behaviour — always rendered when enabled) + // ---------------------------------------------------------------- + if (config.yesDance()) { drawTile(graphics, config.tile1(), Color.GREEN, "Tile 1", new BasicStroke(2)); drawTile(graphics, config.tile2(), Color.GREEN, "Tile 2", new BasicStroke(2)); } - } catch(Exception ex) { + + // ---------------------------------------------------------------- + // Debug panel — only rendered when debug mode is on + // ---------------------------------------------------------------- + if (config.debugMode()) { + renderDebugPanel(graphics); + } + + } catch (Exception ex) { Microbot.logStackTrace(this.getClass().getSimpleName(), ex); } return null; } - private void drawTile(Graphics2D graphics, WorldPoint point, Color color, @Nullable String label, Stroke borderStroke) + + // ------------------------------------------------------------------------- + // DEBUG PANEL + // ------------------------------------------------------------------------- + + /** + * Renders a compact diagnostic panel in the top-left corner of the game + * canvas. All data is read atomically from the plugin's public fields so + * this method never touches game state. + * + * Layout (each row is ROW_H px tall): + * ┌─ Combat Hotkeys DEBUG (v1.1.2) ──────────────────────────┐ + * │ Last key received : protectMelee │ + * │ Key age : 0.3 s ago │ + * │ Last action : toggle prayer PROTECT_MELEE │ + * │ Submitted : 4 │ + * │ Succeeded : 4 Failed: 0 │ + * │ Last error : - │ + * │ Logged in : true │ + * │ Thread : AWT-EventQueue-0 │ + * └──────────────────────────────────────────────────────────┘ + * + * Reading this panel tells you at a glance: + * - "Last key received" never updates → keyPressed is not firing; the + * keybind in config does not match what you're pressing, or the + * KeyManager is not registered. + * - "Last key received" updates but "Last action" doesn't → dispatch() + * was reached but the executor was null/shutdown (logged as an error). + * - Submitted increments but Succeeded doesn't → Rs2Prayer.toggle() + * threw; check "Last error" and the RuneLite log. + * - Everything looks fine but prayer doesn't toggle → Rs2Prayer itself + * has a bug (e.g. prayer tab not open, out of prayer points). + */ + private void renderDebugPanel(Graphics2D graphics) { + Font originalFont = graphics.getFont(); + Font monoFont = new Font(Font.MONOSPACED, Font.PLAIN, 11); + graphics.setFont(monoFont); + FontMetrics fm = graphics.getFontMetrics(monoFont); + + // Collect all rows to render first so we can size the background + String keyReceived = plugin.getLastKeyReceived().get(); + String lastAction = plugin.getLastActionDispatched().get(); + int submitted = plugin.getTotalActionsSubmitted().get(); + int succeeded = plugin.getTotalActionsSucceeded().get(); + int failed = plugin.getTotalActionsFailed().get(); + String lastError = plugin.getLastError().get(); + boolean loggedIn = Microbot.isLoggedIn(); + + // Age of last keypress + String keyAge; + long ts = plugin.getLastKeyTimestamp(); + if (ts == 0) { + keyAge = "never"; + } else { + long ageSec = (Instant.now().toEpochMilli() - ts); + keyAge = String.format("%.1f s ago", ageSec / 1000.0); + } + + String[] labels = { + "Last key :", + "Key age :", + "Last action :", + "Submitted :", + "Succeeded :", + "Failed :", + "Last error :", + "Logged in :", + }; + String[] values = { + keyReceived, + keyAge, + lastAction, + String.valueOf(submitted), + String.valueOf(succeeded), + String.valueOf(failed), + lastError, + String.valueOf(loggedIn), + }; + Color[] valueColors = { + VALUE_COLOR, + LABEL_COLOR, + VALUE_COLOR, + VALUE_COLOR, + succeeded > 0 ? OK_COLOR : LABEL_COLOR, + failed > 0 ? ERR_COLOR : LABEL_COLOR, + lastError.equals("-") ? LABEL_COLOR : ERR_COLOR, + loggedIn ? OK_COLOR : ERR_COLOR, + }; + + int rows = labels.length; + int titleH = ROW_H + 4; + int totalH = titleH + rows * ROW_H + PAD * 2; + int x = PANEL_X; + int y = PANEL_Y; + + // Background + graphics.setColor(BG_COLOR); + graphics.fillRoundRect(x, y, PANEL_W, totalH, 8, 8); + + // Border + graphics.setColor(BORDER_COLOR); + graphics.setStroke(new BasicStroke(1.5f)); + graphics.drawRoundRect(x, y, PANEL_W, totalH, 8, 8); + + // Title bar + graphics.setColor(TITLE_COLOR); + Font titleFont = monoFont.deriveFont(Font.BOLD, 11f); + graphics.setFont(titleFont); + String title = "Combat Hotkeys DEBUG v" + CombatHotkeysPlugin.version; + graphics.drawString(title, x + PAD, y + PAD + fm.getAscent()); + graphics.setFont(monoFont); + + // Divider under title + graphics.setColor(BORDER_COLOR); + graphics.setStroke(new BasicStroke(1f)); + graphics.drawLine(x + 1, y + titleH, x + PANEL_W - 1, y + titleH); + + // Data rows + int rowY = y + titleH + PAD; + for (int i = 0; i < rows; i++) { + int baseY = rowY + i * ROW_H + fm.getAscent(); + + // Label (dimmer) + graphics.setColor(LABEL_COLOR); + graphics.drawString(labels[i], x + PAD, baseY); + + // Value (coloured) + graphics.setColor(valueColors[i]); + int labelW = fm.stringWidth(labels[i]); + // Truncate long values so they don't overflow the panel + String val = truncate(values[i], fm, PANEL_W - labelW - PAD * 3); + graphics.drawString(val, x + PAD + labelW + 4, baseY); + } + + graphics.setFont(originalFont); + } + + /** Truncate a string to fit within maxWidth pixels, appending "…" if needed. */ + private String truncate(String s, FontMetrics fm, int maxWidth) { + if (s == null) return "null"; + if (fm.stringWidth(s) <= maxWidth) return s; + while (s.length() > 1 && fm.stringWidth(s + "…") > maxWidth) { + s = s.substring(0, s.length() - 1); + } + return s + "…"; + } + + // ------------------------------------------------------------------------- + // DANCE TILE DRAWING (unchanged from original) + // ------------------------------------------------------------------------- + + private void drawTile(Graphics2D graphics, WorldPoint point, Color color, + @Nullable String label, Stroke borderStroke) { + if (point == null) return; + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) return; - if (point.distanceTo(playerLocation) >= 32) - { - return; - } + if (point.distanceTo(playerLocation) >= 32) return; LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient(), point); - if (lp == null) - { - return; - } + if (lp == null) return; Polygon poly = Perspective.getCanvasTilePoly(Microbot.getClient(), lp); - if (poly != null) - { + if (poly != null) { OverlayUtil.renderPolygon(graphics, poly, color, new Color(0, 0, 0, 50), borderStroke); } - if (!Strings.isNullOrEmpty(label)) - { - Point canvasTextLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, label, 0); - if (canvasTextLocation != null) - { + if (!Strings.isNullOrEmpty(label)) { + Point canvasTextLocation = Perspective.getCanvasTextLocation( + Microbot.getClient(), graphics, lp, label, 0); + if (canvasTextLocation != null) { OverlayUtil.renderTextLocation(graphics, canvasTextLocation, label, color); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java index 54974e744a..8ab43b4ba3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.combathotkeys; import com.google.inject.Provides; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.MenuAction; import net.runelite.api.events.MenuEntryAdded; @@ -24,6 +25,11 @@ import javax.inject.Inject; import java.awt.*; import java.awt.event.KeyEvent; +import java.time.Instant; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import static net.runelite.client.plugins.microbot.util.Global.sleep; @@ -41,7 +47,57 @@ ) @Slf4j public class CombatHotkeysPlugin extends Plugin implements KeyListener { - public static final String version = "1.1.0"; + // v1.1.2 — fix: replaced runOnSeperateThread (silently drops calls when a prior + // task is still running on the shared ClientThread executor) with a + // dedicated single-thread ExecutorService owned by this plugin. + // Added debug logging + on-screen overlay panel to trace hotkey dispatch. + public static final String version = "1.1.2"; + + // ------------------------------------------------------------------------- + // DEBUG STATE — read by CombatHotkeysOverlay to render the debug panel + // ------------------------------------------------------------------------- + + /** True while debug mode is on (toggled via config). */ + @Getter + volatile boolean debugMode = false; + + /** Last hotkey name that reached keyPressed. */ + @Getter + final AtomicReference lastKeyReceived = new AtomicReference<>("-"); + + /** Timestamp of the last keyPressed hit (epoch ms). */ + @Getter + volatile long lastKeyTimestamp = 0; + + /** Last action that was dispatched to the executor. */ + @Getter + final AtomicReference lastActionDispatched = new AtomicReference<>("-"); + + /** How many hotkey actions have been submitted to the executor total. */ + @Getter + final AtomicInteger totalActionsSubmitted = new AtomicInteger(0); + + /** How many hotkey actions completed without throwing. */ + @Getter + final AtomicInteger totalActionsSucceeded = new AtomicInteger(0); + + /** How many hotkey actions threw an exception. */ + @Getter + final AtomicInteger totalActionsFailed = new AtomicInteger(0); + + /** Last error message from the executor, if any. */ + @Getter + final AtomicReference lastError = new AtomicReference<>("-"); + + // ------------------------------------------------------------------------- + // PRIVATE EXECUTOR + // runOnSeperateThread() uses a single scheduledFuture on the ClientThread + // singleton. If *any* other plugin or the script loop has submitted a task + // that hasn't finished yet the gate `if (!scheduledFuture.isDone()) return` + // silently drops our call. A plugin-owned executor has no such contention. + // ------------------------------------------------------------------------- + private ExecutorService hotkeyExecutor; + @Inject private CombatHotkeysConfig config; @@ -62,165 +118,265 @@ CombatHotkeysConfig provideConfig(ConfigManager configManager) { @Inject private CombatHotkeysScript script; + // ------------------------------------------------------------------------- + // LIFECYCLE + // ------------------------------------------------------------------------- @Override protected void startUp() throws AWTException { + hotkeyExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "CombatHotkeys-executor"); + t.setDaemon(true); + return t; + }); + log.info("[CombatHotkeys] Plugin starting — executor created"); + keyManager.registerKeyListener(this); if (overlayManager != null) { overlayManager.add(overlay); } script.run(config); + log.info("[CombatHotkeys] Plugin started successfully (v{})", version); } + @Override protected void shutDown() { script.shutdown(); keyManager.unregisterKeyListener(this); overlayManager.remove(overlay); + + if (hotkeyExecutor != null) { + hotkeyExecutor.shutdownNow(); + hotkeyExecutor = null; + } + log.info("[CombatHotkeys] Plugin shut down"); + } + + // ------------------------------------------------------------------------- + // HELPERS + // ------------------------------------------------------------------------- + + /** + * Submit an action to the plugin-owned executor. + * + * Every submission is logged so we can tell in the debug overlay (and in + * the RuneLite log) whether the keypress is reaching the dispatcher at all, + * whether the executor accepted it, and whether it threw. + */ + private void dispatch(String actionName, Runnable action) { + if (hotkeyExecutor == null || hotkeyExecutor.isShutdown()) { + log.warn("[CombatHotkeys] dispatch('{}') — executor is null/shutdown, ignoring", actionName); + lastError.set("executor null/shutdown for: " + actionName); + return; + } + + lastActionDispatched.set(actionName); + totalActionsSubmitted.incrementAndGet(); + log.debug("[CombatHotkeys] Submitting '{}' to executor", actionName); + + hotkeyExecutor.submit(() -> { + try { + log.debug("[CombatHotkeys] Executing '{}'", actionName); + action.run(); + totalActionsSucceeded.incrementAndGet(); + log.debug("[CombatHotkeys] '{}' completed OK", actionName); + } catch (Exception ex) { + totalActionsFailed.incrementAndGet(); + lastError.set(actionName + ": " + ex.getMessage()); + log.error("[CombatHotkeys] '{}' threw an exception: {}", actionName, ex.getMessage(), ex); + } + }); } + /** Record which key was just pressed and log it. */ + private void recordKeyHit(String keyName) { + lastKeyReceived.set(keyName); + lastKeyTimestamp = Instant.now().toEpochMilli(); + log.debug("[CombatHotkeys] keyPressed matched: '{}' | loggedIn={} | thread={}", + keyName, + Microbot.isLoggedIn(), + Thread.currentThread().getName()); + } + + // ------------------------------------------------------------------------- + // KEY LISTENER + // ------------------------------------------------------------------------- + @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { - if (!Microbot.isLoggedIn()){ + // Refresh debug flag from config on every keypress so toggling it in + // the config panel takes effect immediately without a restart. + debugMode = config.debugMode(); + + if (!Microbot.isLoggedIn()) { + if (debugMode) { + log.debug("[CombatHotkeys] keyPressed — not logged in, ignoring (keyCode={})", e.getKeyCode()); + } return; } - if(config.dance().matches(e)){ + if (config.dance().matches(e)) { + recordKeyHit("dance"); e.consume(); script.dance = !script.dance; + log.debug("[CombatHotkeys] dance toggled -> {}", script.dance); } + // ------------------------------------------------------------------ + // OFFENSIVE PRAYERS + // ------------------------------------------------------------------ if (config.offensiveMeleeKey().matches(e)) { + recordKeyHit("offensiveMelee"); e.consume(); - Rs2Prayer.toggle(config.offensiveMeleePrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveMeleePrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.offensiveRangeKey().matches(e)) { + recordKeyHit("offensiveRange"); e.consume(); - Rs2Prayer.toggle(config.offensiveRangePrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveRangePrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.offensiveMagicKey().matches(e)) { + recordKeyHit("offensiveMagic"); e.consume(); - Rs2Prayer.toggle(config.offensiveMagicPrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveMagicPrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.specialAttackKey().matches(e)) { + recordKeyHit("specialAttack"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Combat.setSpecState(!Rs2Combat.getSpecState()); - return null; - }); + dispatch("toggle spec", () -> Rs2Combat.setSpecState(!Rs2Combat.getSpecState())); } + // ------------------------------------------------------------------ + // DEFENSIVE / PROTECTION PRAYERS + // ------------------------------------------------------------------ if (config.protectFromMagic().matches(e)) { + recordKeyHit("protectMagic"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC); + dispatch("toggle prayer PROTECT_MAGIC", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC)); } if (config.protectFromMissles().matches(e)) { + recordKeyHit("protectRange"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE); + dispatch("toggle prayer PROTECT_RANGE", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE)); } if (config.protectFromMelee().matches(e)) { + recordKeyHit("protectMelee"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE); + dispatch("toggle prayer PROTECT_MELEE", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE)); } + // ------------------------------------------------------------------ + // FOOD & POTIONS + // ------------------------------------------------------------------ if (config.eatBestFood().matches(e)) { + recordKeyHit("eatBestFood"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.useFood(); - return null; - }); + dispatch("eat best food", Rs2Player::useFood); } if (config.eatFastFood().matches(e)) { + recordKeyHit("eatFastFood"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.useFastFood(); - return null; - }); + dispatch("eat fast food", Rs2Player::useFastFood); } if (config.drinkPrayerPotion().matches(e)) { + recordKeyHit("drinkPrayerPotion"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.drinkPrayerPotion(); - return null; - }); + dispatch("drink prayer potion", Rs2Player::drinkPrayerPotion); } + // ------------------------------------------------------------------ + // GEAR SWAPS + // ------------------------------------------------------------------ if (config.gear1().matches(e)) { + recordKeyHit("gear1"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList1()); - return null; - }); + final String list = config.gearList1(); + dispatch("equip gear 1", () -> equipGear(list)); } if (config.gear2().matches(e)) { + recordKeyHit("gear2"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList2()); - return null; - }); + final String list = config.gearList2(); + dispatch("equip gear 2", () -> equipGear(list)); } if (config.gear3().matches(e)) { + recordKeyHit("gear3"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList3()); - return null; - }); + final String list = config.gearList3(); + dispatch("equip gear 3", () -> equipGear(list)); } if (config.gear4().matches(e)) { + recordKeyHit("gear4"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList4()); - return null; - }); + final String list = config.gearList4(); + dispatch("equip gear 4", () -> equipGear(list)); } if (config.gear5().matches(e)) { + recordKeyHit("gear5"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList5()); - return null; - }); + final String list = config.gearList5(); + dispatch("equip gear 5", () -> equipGear(list)); } + // ------------------------------------------------------------------ + // ALCHEMY + // ------------------------------------------------------------------ if (config.highAlchemyKey().matches(e)) { + recordKeyHit("highAlchemy"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Magic.alch(config.itemToAlch(),50, 75); - return null; - }); + final String item = config.itemToAlch(); + dispatch("high alch " + item, () -> Rs2Magic.alch(item, 50, 75)); } } - private void equipGear(String gearListConfig) { + if (gearListConfig == null || gearListConfig.isBlank()) { + log.warn("[CombatHotkeys] equipGear called with empty/null gear list"); + return; + } String[] itemIDs = gearListConfig.split(","); - for (String value : itemIDs) { - int itemId = Integer.parseInt(value); - Rs2Inventory.equip(itemId); - - int delay = Rs2Random.between(0, config.maxDelay()); - sleep(delay); + value = value.trim(); + if (value.isEmpty()) continue; + try { + int itemId = Integer.parseInt(value); + log.debug("[CombatHotkeys] Equipping item id={}", itemId); + Rs2Inventory.equip(itemId); + int delay = Rs2Random.between(0, config.maxDelay()); + sleep(delay); + } catch (NumberFormatException ex) { + log.error("[CombatHotkeys] Invalid item ID in gear list: '{}'", value); + lastError.set("bad gear ID: " + value); + } } } @Override public void keyReleased(KeyEvent e) {} + // ------------------------------------------------------------------------- + // MENU ENTRY EVENTS (dance tile marking) + // ------------------------------------------------------------------------- + @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java index 360f929caf..74b564646a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class ConstructionPlugin extends Plugin { - public static final String version = "1.3.1"; + public static final String version = "1.3.2"; @Inject private net.runelite.client.plugins.microbot.construction.ConstructionConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java index ed7a9275e1..34579c375c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java @@ -1,7 +1,5 @@ package net.runelite.client.plugins.microbot.construction; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.construction.ConstructionConfig; @@ -10,16 +8,15 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.List; @@ -37,23 +34,13 @@ public class ConstructionScript extends Script { private static final List MAHOGANY_TABLE = List.of(13298, 15298); private static final List MYTHICAL_CAPE_MOUNT = List.of(15394, 31986); - public GameObject getClosestTile(List objIDs) { - List objects = Rs2GameObject.getGameObjects(); - GameObject closest = null; - WorldPoint playerLocation = Rs2Player.getWorldLocation(); - - for (GameObject obj : objects) { - if (objIDs.contains(obj.getId())) { - if (closest == null || Rs2Walker.getDistanceBetween(playerLocation, obj.getWorldLocation()) < Rs2Walker.getDistanceBetween(playerLocation, closest.getWorldLocation())) { - closest = obj; - } - } - } - return closest; + public Rs2TileObjectModel getClosestTile(List objIDs) { + int[] ids = objIDs.stream().mapToInt(Integer::intValue).toArray(); + return Microbot.getRs2TileObjectCache().query().withIds(ids).nearest(); } public Rs2NpcModel getButler() { - return Rs2Npc.getNpc("Demon butler"); + return Microbot.getRs2NpcCache().query().withName("Demon butler").nearestOnClientThread(); } public boolean hasDialogueOptionToUnnote() { @@ -144,7 +131,7 @@ public void grabPlanksWhileWeBuild(net.runelite.client.plugins.microbot.construc private void calculateState(net.runelite.client.plugins.microbot.construction.ConstructionConfig config) { boolean hasRequiredPlanks; - NPC butler = getButler(); + Rs2NpcModel butler = getButler(); List objectIDs = List.of(0); switch (config.selectedMode()) { case OAK_DUNGEON_DOOR: @@ -167,11 +154,14 @@ private void calculateState(net.runelite.client.plugins.microbot.construction.Co workingTile = getClosestTile(objectIDs).getWorldLocation(); } - GameObject objOnWorkingTile = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel objOnWorkingTile = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); if (objOnWorkingTile == null || !objectIDs.contains(objOnWorkingTile.getId())) { - // Find new working tile workingTile = getClosestTile(objectIDs).getWorldLocation(); - objOnWorkingTile = Rs2GameObject.getGameObject(workingTile); + objOnWorkingTile = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); } if (objOnWorkingTile.getId() == objectIDs.get(0)) { @@ -188,9 +178,9 @@ private void calculateState(net.runelite.client.plugins.microbot.construction.Co } private void returnToTheHouse(){ - GameObject housePortal = Rs2GameObject.getGameObject("Portal"); + Rs2TileObjectModel housePortal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearestOnClientThread(); if(housePortal != null){ - if(Rs2GameObject.interact(housePortal, "Build mode")){ + if(housePortal.click("Build mode")){ sleepUntil(()-> Rs2Player.getWorldLocation() != null && Rs2Player.getWorldLocation().getRegionX() == 29 && Rs2Player.getWorldLocation().getRegionY() == 89, Rs2Random.between(10000,20000)); @@ -203,7 +193,9 @@ private void returnToTheHouse(){ } private void buildSpace(net.runelite.client.plugins.microbot.construction.ConstructionConfig config, int actionDelay) { - GameObject space = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel space = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); int spaceId = space != null ? space.getId() : -1; char buildKey = '1'; @@ -217,15 +209,12 @@ private void buildSpace(net.runelite.client.plugins.microbot.construction.Constr case MAHOGANY_TABLE: buildKey = '6'; break; - // case MYTHICAL_CAPE: - // buildKey = '4'; - // break; default: return; } if (space == null) return; - if (Rs2GameObject.interact(space, "Build")) { + if (space.click("Build")) { System.out.println("Interacted with build space: " + space.getId()); sleepUntilOnClientThread(this::hasFurnitureInterfaceOpen, 2500); System.out.println("Pressing key: " + buildKey); @@ -238,13 +227,15 @@ private void buildSpace(net.runelite.client.plugins.microbot.construction.Constr } private void removeSpace(net.runelite.client.plugins.microbot.construction.ConstructionConfig config, int actionDelay) { - GameObject builtObject = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel builtObject = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); int spaceId = builtObject != null ? builtObject.getId() : -1; if (builtObject == null) return; if(builtObject.getId() == 15328 || builtObject.getId() == 15403 || builtObject.getId() == 15298 || builtObject.getId() == 31986) return; - if (Rs2GameObject.interact(builtObject, "Remove")) { + if (builtObject.click("Remove")) { System.out.println("Interacted with remove option: " + builtObject.getId()); sleepUntilOnClientThread(() -> hasRemoveInterfaceOpen(config), 2500); Rs2Keyboard.keyPress('1'); @@ -274,12 +265,12 @@ private void butler(net.runelite.client.plugins.microbot.construction.Constructi sleepUntil(()-> Rs2Dialogue.isInDialogue(), Rs2Random.between(2000,5000)); } - if (Rs2Dialogue.isInDialogue() || Rs2Npc.interact(butler, "Talk-to")) { + if (Rs2Dialogue.isInDialogue() || butler.click("Talk-to")) { sleep(500); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleep(400, 1000); if (Rs2Widget.findWidget("Go to the bank...", null) != null) { - Rs2Inventory.useItemOnNpc(config.selectedMode().getPlankItemId() + 1, butler.getId()); // + 1 for noted item + Rs2Inventory.useItemOnNpc(config.selectedMode().getPlankItemId() + 1, butler.getId()); sleepUntilOnClientThread(() -> Rs2Widget.hasWidget("Dost thou wish me to exchange that certificate")); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleepUntilOnClientThread(() -> Rs2Widget.hasWidget("Select an option")); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java index ec9e493ea8..bbb5ebcfcd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.cooking.scripts; import net.runelite.api.AnimationID; -import net.runelite.api.NPC; -import net.runelite.api.TileObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.cooking.AutoCookingConfig; @@ -18,10 +16,11 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.Objects; @@ -95,9 +94,11 @@ public boolean run(AutoCookingConfig config) { return; } - TileObject cookingObject = Rs2GameObject.findObjectById(location.getCookingObjectID()); + Rs2TileObjectModel cookingObject = Microbot.getRs2TileObjectCache().query().withId(location.getCookingObjectID()).nearest(); if (cookingObject == null) { - cookingObject = Rs2GameObject.findGameObjectByLocation(location.getCookingObjectWorldPoint()); + cookingObject = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(location.getCookingObjectWorldPoint())) + .nearest(); } if (cookingObject != null) { @@ -150,13 +151,17 @@ public boolean run(AutoCookingConfig config) { break; case BANKING: if (location == CookingLocation.ROUGES_DEN) { - NPC npc = Rs2Npc.getBankerNPC(); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getNpc() != null && n.getNpc().getComposition() != null + && n.getNpc().getComposition().getActions() != null + && java.util.Arrays.asList(n.getNpc().getComposition().getActions()).contains("Bank")) + .nearest(); if (npc == null) return; - boolean isNPCBankOpen = Rs2Bank.openBank(npc); + boolean isNPCBankOpen = Rs2Bank.openBank(npc.getNpc()); if (!isNPCBankOpen) return; sleepUntil(() -> !Rs2Player.isMoving()); } else { - TileObject nearbyBankObject = Rs2GameObject.findBank(20); + net.runelite.api.TileObject nearbyBankObject = Rs2GameObject.findBank(20); if (nearbyBankObject != null) { int distanceToBank = Rs2Player.getWorldLocation().distanceTo(nearbyBankObject.getWorldLocation()); boolean isBankOpen = Rs2Bank.openBank(nearbyBankObject); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java index b0ad3fe605..4b85c205d4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java @@ -15,8 +15,7 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -32,7 +31,6 @@ import static net.runelite.api.ItemID.*; import static net.runelite.api.gameval.ItemID.CAKE_TIN; import static net.runelite.api.gameval.ItemID.EGG; -import static net.runelite.client.plugins.microbot.util.npc.Rs2Npc.getNpcs; import static net.runelite.client.plugins.microbot.util.player.Rs2Player.toggleRunEnergy; @@ -183,7 +181,7 @@ public boolean run(AutoCookingConfig config) { if (currentCookingLevel < 40 && !Rs2Inventory.contains("Uncooked stew")) { System.out.println("entering prepare stew"); if (!Rs2Player.isAnimating()) { walkToBanker(); - Rs2Npc.getBankerNPC(); + getBankerNPC(); if (!Rs2Bank.isOpen()) {openNearestBank();} sleepUntil(Rs2Bank::isOpen, 30000);} sleep(1200, 1600); @@ -230,9 +228,8 @@ public void walkToBanker() { return; } - // Select a random banker from the list - NPC banker = bankers.get(random.nextInt(bankers.size())); - LocalPoint bankerLocation = banker.getLocalLocation(); + Rs2NpcModel banker = bankers.get(random.nextInt(bankers.size())); + LocalPoint bankerLocation = banker.getNpc().getLocalLocation(); if (bankerLocation == null) { Microbot.log("Failed to get the banker's location."); @@ -283,18 +280,18 @@ public void walkToBanker() { } public static Rs2NpcModel getBankerNPC() { - return Rs2Npc.getNpcs() - .filter(npc -> npc.getComposition() != null && npc.getComposition().getActions() != null && - Arrays.asList(npc.getComposition().getActions()).contains("Bank")) - .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())))) - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Bank")) + .nearest(); } public void openNearestBank() { if (!Rs2Bank.isOpen()) { - Rs2NpcModel nearestBanker = getBankerNPC(); // Find the closest NPC that has "Bank" action + Rs2NpcModel nearestBanker = getBankerNPC(); if (nearestBanker != null) { - Rs2Npc.interact(nearestBanker, "Bank"); + nearestBanker.click("Bank"); sleepUntil(Rs2Bank::isOpen, 5000); } } @@ -327,11 +324,12 @@ private void waitForWalking() { public static List getBankerNPCs() { - return getNpcs() - .filter(value -> (value.getComposition() != null && value.getComposition().getActions() != null && - Arrays.asList(value.getComposition().getActions()).contains("Bank"))) - .limit(4) // Collect only up to 4 bankers - .collect(Collectors.toList()); + List bankers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Bank")) + .toList(); + return bankers.size() > 4 ? bankers.subList(0, 4) : bankers; } @@ -632,54 +630,31 @@ public void findAndInteractWithNearestRange() { } System.out.println("Player location: " + playerPosition); - // Step 2: Retrieve all game objects within 15 tiles of the player - List nearbyObjects = Rs2GameObject.getGameObjects(); - if (nearbyObjects == null || nearbyObjects.isEmpty()) { - System.out.println("Found 0 nearby objects."); + int[] rangeObjectIds = getRangeObjectIds(); + net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel rangeObj = Microbot.getRs2TileObjectCache().query() + .withIds(rangeObjectIds) + .nearest(); + if (rangeObj == null) { + System.out.println("No range found within nearby tiles."); return; } - // Step 3: Fetch the list of range object IDs from the getRangeObjectIds method - int[] rangeObjectIds = getRangeObjectIds(); - - // Step 4: Iterate over nearby objects to find any matching range or stove - boolean rangeFound = false; - for (GameObject obj : nearbyObjects) { - int objId = obj.getId(); - - // Step 5: Check if the object ID matches any of the range object IDs - if (Arrays.stream(rangeObjectIds).anyMatch(id -> id == objId)) { - - Rs2Camera.turnTo(obj.getLocalLocation()); - - - // If a match is found, interact with the object using the "Cook" action - boolean interactionSuccess = Rs2GameObject.interact(obj, "Cook"); - if (interactionSuccess) { - System.out.println("Successfully interacted with object ID: " + objId + " using 'Cook'"); - } else { - System.out.println("Failed to interact with object ID: " + objId); - } + Rs2Camera.turnTo(rangeObj.getLocalLocation()); - // Step 6: Wait until the player stops moving and the cooking widget appears - boolean widgetAppeared = sleepUntil(() -> !Rs2Player.isMoving() && - Rs2Widget.findWidget("How many would you like to cook?", null, false) != null, 35000); - if (widgetAppeared) { - System.out.println("Cooking widget appeared, pressing space to confirm."); - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - } else { - System.out.println("Cooking widget did not appear."); - } - - rangeFound = true; - break; - } else { - System.out.println("No match for object ID: " + objId); - } + boolean interactionSuccess = rangeObj.click("Cook"); + if (interactionSuccess) { + System.out.println("Successfully interacted with range using 'Cook'"); + } else { + System.out.println("Failed to interact with range"); } - if (!rangeFound) { - System.out.println("No range found within 15 tiles."); + boolean widgetAppeared = sleepUntil(() -> !Rs2Player.isMoving() && + Rs2Widget.findWidget("How many would you like to cook?", null, false) != null, 35000); + if (widgetAppeared) { + System.out.println("Cooking widget appeared, pressing space to confirm."); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } else { + System.out.println("Cooking widget did not appear."); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java index 26f8b6fcc3..c4a47e465e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java @@ -12,7 +12,8 @@ public enum FlaxSpinLocations { FALADOR("Falador", new WorldPoint(2982, 3314, 0), ObjectID.SPINNING_WHEEL_14889), SEERS_VILLAGE("Seers Village", new WorldPoint(2711, 3471, 1), ObjectID.SPINNING_WHEEL_25824), RELLEKKA("Rellekka", new WorldPoint(2617, 3660, 0), ObjectID.SPINNING_WHEEL), - TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold", new WorldPoint(2488, 3409, 1), ObjectID.SPINNING_WHEEL_14889); + TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold", new WorldPoint(2488, 3409, 1), ObjectID.SPINNING_WHEEL_14889), + NEMUS_RETREAT("Nemus Retreat", new WorldPoint(1373, 3313, 0), 55330); // LUMBRIDGE_CASTLE(new WorldPoint(3209, 3213, 1), ObjectID.SPINNING_WHEEL_14889), Issue with web-walker when banking diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java index 2b0d89475b..338402bf14 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java @@ -3,7 +3,7 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.ItemID; import net.runelite.api.Skill; -import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.crafting.jewelry.enums.*; @@ -14,6 +14,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch; @@ -308,10 +309,17 @@ public boolean run() { break; case CRAFTING: - TileObject furnaceObject = Rs2GameObject.findObjectById(plugin.getCraftingLocation().getFurnanceObjectID()); - + WorldPoint furnaceLocation = plugin.getCraftingLocation().getFurnaceLocation(); + WorldPoint anchor = furnaceLocation != null ? furnaceLocation : Rs2Player.getWorldLocation(); + Rs2TileObjectModel furnaceObject = Microbot.getRs2TileObjectCache().query() + .withName("Furnace") + .where(o -> Rs2GameObject.hasAction(o, "Smelt")) + .nearest(anchor, 20); + if (furnaceObject == null) { - Rs2Walker.walkTo(plugin.getCraftingLocation().getFurnaceLocation()); + if (furnaceLocation != null) { + Rs2Walker.walkTo(furnaceLocation); + } return; } @@ -320,7 +328,7 @@ public boolean run() { return; } - Rs2GameObject.interact(furnaceObject, "smelt"); + furnaceObject.click("smelt"); sleepUntilTrue(() -> Rs2Widget.isGoldCraftingWidgetOpen() || Rs2Widget.isSilverCraftingWidgetOpen(), 500, 20000); Rs2Widget.clickWidget(plugin.getJewelry().getItemName()); Rs2Antiban.actionCooldown(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java index e806d926f7..eb9cd14584 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.runelite.api.ObjectID; import net.runelite.api.Quest; import net.runelite.api.QuestState; import net.runelite.api.coords.WorldPoint; @@ -12,17 +11,18 @@ @Getter @RequiredArgsConstructor public enum CraftingLocation { - - EDGEVILLE(new WorldPoint(3109, 3499, 0), ObjectID.FURNACE_16469, BankLocation.EDGEVILLE), - PORT_PHASMATYS(new WorldPoint(3687, 3479, 0), ObjectID.FURNACE_24009, BankLocation.PORT_PHASMATYS), - MOUNT_KARUULM(new WorldPoint(1324, 3808, 0), ObjectID.VOLCANIC_FURNACE, BankLocation.MOUNT_KARUULM), - ZANARIS(new WorldPoint(2401, 4473, 0), ObjectID.FURNACE_12100, BankLocation.ZANARIS), - FALADOR(new WorldPoint(2975, 3369, 0), ObjectID.FURNACE_24009, BankLocation.FALADOR_WEST); - + + ANYWHERE(null, null), + EDGEVILLE(new WorldPoint(3109, 3499, 0), BankLocation.EDGEVILLE), + PORT_PHASMATYS(new WorldPoint(3687, 3479, 0), BankLocation.PORT_PHASMATYS), + MOUNT_KARUULM(new WorldPoint(1324, 3808, 0), BankLocation.MOUNT_KARUULM), + ZANARIS(new WorldPoint(2401, 4473, 0), BankLocation.ZANARIS), + FALADOR(new WorldPoint(2975, 3369, 0), BankLocation.FALADOR_WEST), + SHILO_VILLAGE(new WorldPoint(2856, 2967, 0), BankLocation.SHILO_VILLAGE); + private final WorldPoint furnaceLocation; - private final int furnanceObjectID; private final BankLocation bankLocation; - + public boolean hasRequirements() { switch (this) { case PORT_PHASMATYS: diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java index 23f2a8bf6e..0b15565f78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java @@ -1,12 +1,10 @@ package net.runelite.client.plugins.microbot.crafting.scripts; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.crafting.CraftingConfig; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -15,7 +13,8 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.event.KeyEvent; -import java.util.*; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.TimeUnit; enum State { @@ -99,9 +98,9 @@ public boolean run(CraftingConfig config) { Rs2Walker.walkTo(config.flaxSpinLocation().getWorldPoint(), 4); sleepUntilTrue(() -> isNearSpinningWheel(config, 4) && !Rs2Player.isMoving(), 600, 300000); if (!isNearSpinningWheel(config, 4)) return; - Optional spinningWheel = Rs2GameObject.getGameObjects().stream() - .filter(obj -> obj.getId() == config.flaxSpinLocation().getObjectID()).min(Comparator.comparingInt(obj -> Rs2Player.getWorldLocation().distanceTo(obj.getWorldLocation()))); - if (spinningWheel.isEmpty()) { + net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel spinningWheel = Microbot.getRs2TileObjectCache().query() + .withId(config.flaxSpinLocation().getObjectID()).nearest(); + if (spinningWheel == null) { Rs2Walker.walkFastCanvas(config.flaxSpinLocation().getWorldPoint()); return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java b/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java index c7de81ab96..51ae9a0618 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java @@ -9,11 +9,9 @@ import net.runelite.api.gameval.VarbitID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -36,7 +34,7 @@ public enum DailyTask { && Microbot.getClient().getVarpValue(VarPlayerID.NZONE_REWARDPOINTS) >= 9500 && Microbot.getClient().getVarbitValue(VarbitID.NZONE_HERBBOXES_PURCHASED) < 15, () -> { - Rs2GameObject.interact(26273, "Search"); + Microbot.getRs2TileObjectCache().query().interact(26273, "Search"); sleepUntil(() -> Rs2Widget.findWidget("Dom Onion") != null); doInvoke(new NewMenuEntry("Buy-50", "Herb box", 4, MenuAction.CC_OP, 20, 13500420, false), new Rectangle(1, 1)); Rs2Inventory.waitForInventoryChanges(1000); @@ -53,7 +51,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_VARROCK_EASY) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_STAVES_COLLECTED) == 0, () -> { - Rs2GameObject.interact(30357); + Microbot.getRs2TileObjectCache().query().interact(30357); sleepUntil(() -> Rs2Widget.findWidget("discounted battlestaves") != null); Rs2Widget.clickWidget("Click here to continue"); sleepUntil(() -> Rs2Widget.findWidget("Yes") != null); @@ -69,7 +67,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_ESSENCE_COLLECTED) == 0, () -> { - Rs2Npc.interact(8481, "Claim"); + Microbot.getRs2NpcCache().query().withId(8481).interact("Claim"); Rs2Inventory.waitForInventoryChanges(1000); }, DailyTasksConfig::collectEssence @@ -92,7 +90,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_KANDARIN_EASY) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_FLAX_STATE) == 0, () -> { - Rs2Npc.interact(5522, "Exchange"); + Microbot.getRs2NpcCache().query().withId(5522).interact("Exchange"); sleepUntil(Rs2Dialogue::isInDialogue); Rs2Dialogue.clickOption("Agree"); Rs2Inventory.waitForInventoryChanges(1000); @@ -140,10 +138,10 @@ public enum DailyTask { () -> Microbot.getClient().getVarpValue(VarPlayer.THRONE_OF_MISCELLANIA) > 0, () -> { sleepUntil(() -> Rs2Player.getWorldLocation().getRegionID() == 10044, 10000); - Rs2GameObject.interact(15079); + Microbot.getRs2TileObjectCache().query().interact(15079); sleepUntilOnClientThread(() -> Microbot.getClient().getVarbitValue(Varbits.KINGDOM_APPROVAL) == 127, 10000); Rs2Walker.walkTo(new WorldPoint(2502, 3858, 1), 5); - Rs2Npc.interact(5448, "Collect"); + Microbot.getRs2NpcCache().query().withId(5448).interact("Collect"); sleepUntil(() -> Rs2Dialogue.hasDialogueOption("Collect resources")); Rs2Dialogue.clickOption("Collect resources"); sleepUntil(() -> Rs2Widget.findWidget("Resources Collected") != null); diff --git a/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java b/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java index e357f52fa3..911f65549a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java @@ -6,8 +6,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.delveprayerhelper.enums.DelvePrayerHelperState; import net.runelite.client.plugins.microbot.delveprayerhelper.enums.DelvePrayerHelperProjectile; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -51,11 +50,11 @@ public boolean run() { void handleProjectiles() { int currentCycle = Microbot.getClient().getGameCycle(); - Rs2NpcModel boss = Rs2Npc.getNpc("Doom of Mokhaiotl", false); + Rs2NpcModel boss = Microbot.getRs2NpcCache().query().where(n -> n.getName() != null && n.getName().contains("Doom of Mokhaiotl")).nearest(); incomingProjectiles.entrySet().removeIf(e -> e.getKey() < currentCycle); - boolean bossAlive = boss != null && !boss.isDead(); + boolean bossAlive = boss != null && !boss.getNpc().isDead(); if (!bossAlive) { Rs2Prayer.disableAllPrayers(); @@ -124,8 +123,9 @@ else if (projectile.getId() == DelvePrayerHelperProjectile.RANGE.getProjectileID private void toggleOffensivePrayer() { if(config.offensivePrayer()) { - Rs2Prayer.toggle(Rs2Prayer.getBestRangePrayer(), !config.noOffensivePrayerInShieldPhase() - || !Rs2Npc.getNpc("Doom of Mokhaiotl", false).getName().contains("(Shielded)")); + var doom = Microbot.getRs2NpcCache().query().where(n -> n.getName() != null && n.getName().contains("Doom of Mokhaiotl")).nearest(); + boolean shielded = doom != null && doom.getName().contains("(Shielded)"); + Rs2Prayer.toggle(Rs2Prayer.getBestRangePrayer(), !config.noOffensivePrayerInShieldPhase() || !shielded); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java b/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java index 3a2d5997d3..b54b15a7f3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.driftnet; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; @@ -17,7 +16,7 @@ import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -26,8 +25,8 @@ import java.awt.event.KeyEvent; import java.util.List; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; import static net.runelite.client.plugins.microbot.Microbot.log; @@ -134,8 +133,7 @@ public void reEnterArea(){ if(driftnetPastFirstTunnelWP.equals(Rs2Player.getWorldLocation())){ Microbot.log("Navigating the Plant door"); - WallObject plantDoor = Rs2GameObject.getWallObject(30961); - Rs2GameObject.interact(plantDoor, "Navigate"); + Microbot.getRs2TileObjectCache().query().withId(30961).interact("Navigate"); sleepUntil(()-> Rs2Player.isAnimating(), Rs2Random.between(2000,5000)); sleepUntil(()-> !Rs2Player.isAnimating(), Rs2Random.between(2000,5000)); } @@ -164,7 +162,7 @@ public void reEnterArea(){ private void fetchNetsFromAnnette() { final int maxWeight = 25; // https://oldschool.runescape.wiki/w/Drift_net_fishing var maxDriftnets = maxWeight - Microbot.getClient().getWeight() - 1; // Driftnets are 1kg each; doing - 1 to be safe - Rs2GameObject.interact(ObjectID.FOSSIL_MERMAID_DRIFTNETS, "Nets"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.FOSSIL_MERMAID_DRIFTNETS).interact("Nets"); sleepUntil(() -> Rs2Widget.getWidget(20250629) != null); var annetteWidget = Rs2Widget.getWidget(20250629); var annetteWithdrawXMenuEntry = new NewMenuEntry(0, 20250629, 57, 3, 21652, "Drift net"); @@ -245,7 +243,7 @@ private void handleUnsetNet(DriftNet net) { */ private void chaseNearbyFish(Set fishSet) { // Sort the NPC indexes by distance to the player - var fishNpcs = Rs2Npc.getNpcs(NpcID.FOSSIL_FISH_SHOAL).collect(Collectors.toList()); + var fishNpcs = Microbot.getRs2NpcCache().query().withId(NpcID.FOSSIL_FISH_SHOAL).toList(); var fishIndexNpcMap = new HashMap(); fishSet.forEach(index -> { var fishNpc = fishNpcs.stream().filter(npc -> npc.getIndex() == index).findFirst().orElse(null); @@ -270,7 +268,7 @@ private void chaseNearbyFish(Set fishSet) { if (npc == null) continue; // Interact with the fish to "Chase" it - Rs2Npc.interact(npc, "Chase"); + npc.click("Chase"); sleepGaussian(1500, 300); break; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java index db2081f5df..9a988a62ea 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.eelfishing; -import net.runelite.api.NPC; import net.runelite.api.gameval.ItemID; import net.runelite.client.game.FishingSpot; import net.runelite.client.plugins.microbot.Microbot; @@ -14,7 +13,7 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import java.util.concurrent.TimeUnit; @@ -60,10 +59,10 @@ public boolean run(EelFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if (Rs2Npc.interact(fishingspot)) { + if (fishingspot.click()) { Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); } @@ -78,13 +77,9 @@ public void onGameTick() { } private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : getFishingSpotIds(config.fishingSpot())) { - Rs2NpcModel fishingspot = Rs2Npc.getNpc(fishingSpotId); - if (fishingspot != null) { - return fishingspot; - } - } - return null; + int[] ids = getFishingSpotIds(config.fishingSpot()); + if (ids.length == 0) return null; + return Microbot.getRs2NpcCache().query().withIds(ids).nearest(); } private int[] getFishingSpotIds(EelFishingSpot spot) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java b/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java index 121d269a82..6ec3500d77 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java @@ -2,10 +2,11 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; public class DismissNpcEvent implements BlockingEvent { @@ -15,18 +16,24 @@ public DismissNpcEvent(EventDismissConfig config) { this.config = config; } + private Rs2NpcModel getRandomEventNpc() { + var oldModel = Rs2Npc.getRandomEventNPC(); + if (oldModel == null) return null; + return Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(oldModel.getRuneliteNpc())).nearest(); + } + @Override public boolean validate() { - Rs2NpcModel randomEventNPC = Rs2Npc.getRandomEventNPC(); + Rs2NpcModel randomEventNPC = getRandomEventNpc(); if (randomEventNPC == null) { return false; } - return Rs2Npc.hasLineOfSight(randomEventNPC); + return randomEventNPC.hasLineOfSight(); } @Override public boolean execute() { - Rs2NpcModel npc = Rs2Npc.getRandomEventNPC(); + Rs2NpcModel npc = getRandomEventNpc(); if (npc == null) return true; @@ -51,13 +58,13 @@ public BlockingEventPriority priority() { } private void talkTo(Rs2NpcModel npc) { - Rs2Npc.interact(npc, "Talk-to"); + npc.click("Talk-to"); Rs2Dialogue.sleepUntilHasContinue(); Rs2Dialogue.clickContinue(); } private void dismiss(Rs2NpcModel npc) { - Rs2Npc.interact(npc, "Dismiss"); - Global.sleepUntil(() -> Rs2Npc.getRandomEventNPC() == null); + npc.click("Dismiss"); + Global.sleepUntil(() -> getRandomEventNpc() == null); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java index 640e838801..a65ebed97b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.farmtreerun; import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; import net.runelite.client.plugins.microbot.farmtreerun.enums.FruitTreeEnum; import net.runelite.client.plugins.microbot.farmtreerun.enums.HardTreeEnums; import net.runelite.client.plugins.microbot.farmtreerun.enums.TreeEnums; @@ -26,11 +27,13 @@ "

    \n" + "
  1. Taverley teleport tab
  2. \n" + "
  3. Skills necklace (2 to 6)
  4. \n" + + "
  5. Construction cape (at 99 Construction) - auto-grabbed for POH teleports
  6. \n" + + "
  7. Crystal tree sapling (for Prifddinas patch, requires Song of the Elves)
  8. \n" + "
" + "
Optional:\n" + "
    \n" + "
  1. Items for protection payment
  2. \n" + - "
  3. Filled Bottomless compost bucket
  4. \n" + + "
  5. Compost / Supercompost / Ultracompost / Bottomless compost bucket
  6. \n" + "
" + "
Extra information:\n" + "
If you want to stop the script during your farm run (maybe it gets stuck or whatever reason), make sure to disable 'Banking' and disable patches you previously ran." + @@ -89,29 +92,56 @@ public interface FarmTreeRunConfig extends Config { /* ========================= * Sapling selection * ========================= */ + @ConfigItem( + keyName = "enableTrees", + name = "Run trees", + description = "Enable regular tree patches", + position = 0, + section = saplingSection + ) + default boolean enableTrees() { return true; } + @ConfigItem( keyName = "treeSapling", name = "Tree sapling", description = "Select tree sapling to use", - position = 0, + position = 1, section = saplingSection ) default TreeEnums selectedTree() { return TreeEnums.MAPLE; } + @ConfigItem( + keyName = "enableFruitTrees", + name = "Run fruit trees", + description = "Enable fruit tree patches", + position = 2, + section = saplingSection + ) + default boolean enableFruitTrees() { return true; } + @ConfigItem( keyName = "fruitTreeSapling", name = "Fruit tree sapling", description = "Select fruit tree sapling to use", - position = 1, + position = 3, section = saplingSection ) default FruitTreeEnum selectedFruitTree() { return FruitTreeEnum.PAPAYA; } + @ConfigItem( + keyName = "enableHardTrees", + name = "Run hardwood trees", + description = "Enable hardwood tree patches", + position = 4, + section = saplingSection + ) + default boolean enableHardTrees() { return true; } + @ConfigItem( keyName = "Fossil Island Tree", name = "Hard sapling", description = "Select Hard tree sapling to use", - position = 2, + position = 5, section = saplingSection ) default HardTreeEnums selectedHardTree() { return HardTreeEnums.MAHOGANY; } @@ -159,13 +189,13 @@ public interface FarmTreeRunConfig extends Config { default boolean banking() { return true; } @ConfigItem( - keyName = "useCompost", - name = "Use compost", - description = "Only bottomless compost bucket is supported", + keyName = "compostType", + name = "Compost type", + description = "Select compost type. Only applied at patches without protection enabled.", position = 1, section = gearSection ) - default boolean useCompost() { return true; } + default CompostType compostType() { return CompostType.NONE; } @ConfigItem( keyName = "useGraceful", @@ -261,6 +291,15 @@ public interface FarmTreeRunConfig extends Config { ) default boolean auburnTreePatch() { return true; } + @ConfigItem( + keyName = "priffddinasCrystalTree", + name = "Prifddinas (Crystal)", + description = "Prifddinas Crystal tree patch (requires 74 Farming and Song of the Elves). Uses a Crystal tree sapling.", + position = 7, + section = treePatchesSection + ) + default boolean priffddinasCrystalTreePatch() { return false; } + /* ========================= * Fruit tree patches — ordered to match run: * GS Fruit → TGV Fruit → Farming Guild Fruit → Brimhaven → Catherby → Lletya → Kastori diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java index ad6af4e631..7963f01659 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class FarmTreeRunPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.2.0"; @Inject private FarmTreeRunConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index 3a5a7363e6..c8e8925b9e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -5,7 +5,9 @@ import net.runelite.api.*; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Rs2Leprechaun; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; import net.runelite.client.plugins.microbot.farmtreerun.enums.HardTreeEnums; import net.runelite.client.plugins.microbot.farmtreerun.enums.FruitTreeEnum; import net.runelite.client.plugins.microbot.farmtreerun.enums.TreeEnums; @@ -82,6 +84,7 @@ public enum Patch { FOSSIL_TREE_PATCH_C(30481, new WorldPoint(3701, 3840, 0), TreeKind.HARD_TREE, 1, 0), AUBURNVALE_TREE_PATCH(56953, new WorldPoint(1365, 3320, 0), TreeKind.TREE, 1, 0), KASTORI_FRUIT_TREE_PATCH(56955, new WorldPoint(1349, 3058, 0), TreeKind.FRUIT_TREE, 1, 12765), + PRIFFDDINAS_CRYSTAL_TREE_PATCH(34906, new WorldPoint(3291, 6117, 0), TreeKind.TREE, 74, 0), AVIUM_SAVANNAH_HARDWOOD_PATCH(50692, new WorldPoint(1684, 2974, 0), TreeKind.HARD_TREE,1,0); private final int id; @@ -119,8 +122,9 @@ public boolean run(FarmTreeRunConfig config) { } calculatePatches(config); checkSaplingLevelRequirement(config); + if (!validateSpecialPatches(config)) return; - dropEmptyPlantPots(); + dropCrap(); Patch patch = null; boolean handledPatch = false; @@ -130,14 +134,14 @@ public boolean run(FarmTreeRunConfig config) { bank(config); } else { if (isCompostEnabled(config)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; + compostItemId = config.compostType().getItemId(); } botStatus = net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState.HANDLE_GNOME_STRONGHOLD_FRUIT_PATCH; } break; case HANDLE_GNOME_STRONGHOLD_FRUIT_PATCH: patch = Patch.GNOME_STRONGHOLD_FRUIT_TREE_PATCH; - if (config.gnomeStrongholdFruitTreePatch()) { + if (config.enableFruitTrees() && config.gnomeStrongholdFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -148,7 +152,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_GNOME_STRONGHOLD_TREE_PATCH: patch = Patch.GNOME_STRONGHOLD_TREE_PATCH; - if (config.gnomeStrongholdTreePatch()) { + if (config.enableTrees() && config.gnomeStrongholdTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -159,7 +163,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FARMING_GUILD_TREE_PATCH: patch = Patch.FARMING_GUILD_TREE_PATCH; - if (config.farmingGuildTreePatch() && patch.hasRequiredLevel()) { + if (config.enableTrees() && config.farmingGuildTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -170,7 +174,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FARMING_GUILD_FRUIT_PATCH: patch = Patch.FARMING_GUILD_FRUIT_TREE_PATCH; - if (config.farmingGuildFruitTreePatch() && patch.hasRequiredLevel()) { + if (config.enableFruitTrees() && config.farmingGuildFruitTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -181,7 +185,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_BRIMHAVEN_FRUIT_TREE_PATCH: patch = Patch.BRIMHAVEN_FRUIT_TREE_PATCH; - if (config.brimhavenFruitTreePatch()) { + if (config.enableFruitTrees() && config.brimhavenFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -192,7 +196,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_TREE_GNOME_VILLAGE_FRUIT_TREE_PATCH: patch = Patch.TREE_GNOME_VILLAGE_FRUIT_TREE_PATCH; - if (config.treeGnomeVillageFruitTreePatch()) { + if (config.enableFruitTrees() && config.treeGnomeVillageFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -203,7 +207,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_TAVERLEY_TREE_PATCH: patch = Patch.TAVERLEY_TREE_PATCH; - if (config.taverleyTreePatch()) { + if (config.enableTrees() && config.taverleyTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -213,7 +217,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FALADOR_TREE_PATCH: patch = Patch.FALADOR_TREE_PATCH; - if (config.faladorTreePatch()) { + if (config.enableTrees() && config.faladorTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -223,7 +227,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_LUMBRIDGE_TREE_PATCH: patch = Patch.LUMBRIDGE_TREE_PATCH; - if (config.lumbridgeTreePatch()) { + if (config.enableTrees() && config.lumbridgeTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -234,7 +238,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_VARROCK_TREE_PATCH: patch = Patch.VARROCK_TREE_PATCH; - if (config.varrockTreePatch()) { + if (config.enableTrees() && config.varrockTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -245,7 +249,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_CATHERBY_FRUIT_TREE_PATCH: patch = Patch.CATHERBY_FRUIT_TREE_PATCH; - if (config.catherbyFruitTreePatch()) { + if (config.enableFruitTrees() && config.catherbyFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -256,7 +260,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_A: patch = Patch.FOSSIL_TREE_PATCH_A; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -267,7 +271,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_B: patch = Patch.FOSSIL_TREE_PATCH_B; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -278,7 +282,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_C: patch = Patch.FOSSIL_TREE_PATCH_C; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -289,7 +293,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_LLETYA_FRUIT_TREE_PATCH: patch = Patch.LLETYA_FRUIT_TREE_PATCH; - if (config.lletyaFruitTreePatch()) { + if (config.enableFruitTrees() && config.lletyaFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -301,7 +305,7 @@ public boolean run(FarmTreeRunConfig config) { case HANDLE_AUBURNVALE_TREE_PATCH: { patch = Patch.AUBURNVALE_TREE_PATCH; - if (config.auburnTreePatch()) { + if (config.enableTrees() && config.auburnTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -312,7 +316,18 @@ public boolean run(FarmTreeRunConfig config) { } case HANDLE_KASTORI_FRUIT_TREE_PATCH: { patch = Patch.KASTORI_FRUIT_TREE_PATCH; - if (config.kastoriFruitTreePatch()) { + if (config.enableFruitTrees() && config.kastoriFruitTreePatch()) { + if (walkToLocation(patch.getLocation())) { + handledPatch = handlePatch(config, patch); + } + if (!handledPatch) return; + } + botStatus = net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState.HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH; + break; + } + case HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH: { + patch = Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH; + if (config.enableTrees() && config.priffddinasCrystalTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -324,7 +339,7 @@ public boolean run(FarmTreeRunConfig config) { case HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH: { patch = Patch.AVIUM_SAVANNAH_HARDWOOD_PATCH; - if (config.aviumSavannahHardwoodPatch()) { + if (config.enableHardTrees() && config.aviumSavannahHardwoodPatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -336,6 +351,13 @@ public boolean run(FarmTreeRunConfig config) { case FINISHED: + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.walkToBank()) return; + if (!Rs2Bank.openBank()) return; + } + Rs2Bank.depositAll(); + sleepUntil(() -> Rs2Inventory.isEmpty(), 3000); + Rs2Bank.closeBank(); Microbot.getClientThread().runOnClientThreadOptional(() -> { Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Tree run completed.", "Acun", false); Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Made with love by Acun.", "Acun", false); @@ -365,6 +387,23 @@ private void calculatePatches(FarmTreeRunConfig config) { } } + private boolean validateSpecialPatches(FarmTreeRunConfig config) { + if (config.priffddinasCrystalTreePatch()) { + int farmingLevel = Rs2Player.getRealSkillLevel(Skill.FARMING); + if (farmingLevel < 74) { + Microbot.showMessage("Prifddinas Crystal tree requires 74 Farming (you have " + farmingLevel + "). Disable the Prifddinas patch or train Farming before starting. Shutting down."); + shutdown(); + return false; + } + if (Rs2Player.getQuestState(Quest.SONG_OF_THE_ELVES) != QuestState.FINISHED) { + Microbot.showMessage("Prifddinas Crystal tree requires Song of the Elves to be completed. Disable the Prifddinas patch before starting. Shutting down."); + shutdown(); + return false; + } + } + return true; + } + private void checkSaplingLevelRequirement(FarmTreeRunConfig config) { if (!getSelectedTreePatches(config).isEmpty()) config.selectedTree().hasRequiredLevel(); @@ -373,29 +412,26 @@ private void checkSaplingLevelRequirement(FarmTreeRunConfig config) { config.selectedFruitTree().hasRequiredLevel(); } - private void dropEmptyPlantPots() { - int emptyPlantPot = ItemID.EMPTY_PLANT_POT; - if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !Rs2Player.isInteracting()) { - if (Rs2Inventory.hasItem(emptyPlantPot)) { - Rs2Inventory.dropAll(emptyPlantPot); - sleepUntil(() -> !Rs2Inventory.hasItem(emptyPlantPot), 8000); + private void dropCrap() { + int[] junk = {ItemID.EMPTY_PLANT_POT, ItemID.BUCKET, ItemID.WEEDS}; + for (int id : junk) { + if (Rs2Inventory.hasItem(id)) { + Rs2Inventory.dropAll(id); + sleepUntil(() -> !Rs2Inventory.hasItem(id), 8000); } } } private boolean walkToLocation(WorldPoint location) { - if (!Rs2Player.isAnimating()) { - Rs2Walker.walkTo(location); - sleepUntil(() -> Rs2Player.distanceTo(location) < 16); - return Rs2Player.distanceTo(location) < 16; - } - return false; + Rs2Walker.walkTo(location); + sleepUntil(() -> Rs2Player.distanceTo(location) < 16); + return Rs2Player.distanceTo(location) < 16; } private void bank(FarmTreeRunConfig config) { items.clear(); if (Rs2Bank.openBank() || Rs2Bank.walkToBank()) { - sleepUntil(() -> !Rs2Player.isAnimating()); + sleepUntil(Rs2Bank::isOpen, 5000); if (!Rs2Bank.isOpen()) return; sleep(600, 2200); @@ -428,11 +464,15 @@ private void bank(FarmTreeRunConfig config) { } if (isCompostEnabled(config)) { - if (Rs2Bank.hasItem(ItemID.BOTTOMLESS_COMPOST_BUCKET_22997)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; - items.add(new FarmingItem(compostItemId, 1)); - } else { - Microbot.log("Only bottomless compost is supported. Skipping composting."); + CompostType compostType = config.compostType(); + compostItemId = compostType.getItemId(); + if (compostType.isReusable()) { + if (Rs2Bank.hasItem(compostItemId)) { + items.add(new FarmingItem(compostItemId, 1)); + } else { + Microbot.log("Bottomless compost bucket not found in bank. Skipping composting."); + compostItemId = null; + } } } @@ -450,19 +490,30 @@ private void bank(FarmTreeRunConfig config) { } } + // Construction cape (99 Construction): useful for POH/teleport options + if (Rs2Player.getRealSkillLevel(Skill.CONSTRUCTION) >= 99) { + if (Rs2Bank.hasItem(ItemID.CONSTRUCT_CAPET)) { + items.add(new FarmingItem(ItemID.CONSTRUCT_CAPET, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.CONSTRUCT_CAPE)) { + items.add(new FarmingItem(ItemID.CONSTRUCT_CAPE, 1, false, true)); + } + } + if (config.useSkillsNecklace() && (config.farmingGuildTreePatch() || config.farmingGuildFruitTreePatch())) { - if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE2)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE2, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE3)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE3, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE4)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE4, 1)); + if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE6)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE6, 1, false, true)); } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE5)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE5, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE6)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE6, 1)); + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE5, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE4)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE4, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE3)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE3, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE2)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE2, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE1)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE1, 1, false, true)); } else { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE1, 2)); + Microbot.log("No skills necklace found in bank. Skipping."); } } @@ -475,8 +526,16 @@ private void bank(FarmTreeRunConfig config) { int fruitTreeSaplingsCount = getSelectedFruitTreePatches(config).size(); int hardTreeSaplingsCount = getSelectedHardTreePatches(config).size(); - if (treeSaplingsCount > 0) - items.add(new FarmingItem(selectedTree.getSaplingId(), treeSaplingsCount)); + // Crystal tree patch uses its own sapling, not the selected regular tree sapling + boolean priffEnabled = config.priffddinasCrystalTreePatch() + && Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH.hasRequiredLevel(); + int regularTreeSaplingsCount = priffEnabled ? treeSaplingsCount - 1 : treeSaplingsCount; + + if (regularTreeSaplingsCount > 0) + items.add(new FarmingItem(selectedTree.getSaplingId(), regularTreeSaplingsCount)); + + if (priffEnabled) + items.add(new FarmingItem(ItemID.CRYSTAL_SAPLING, 1)); if (fruitTreeSaplingsCount > 0) items.add(new FarmingItem(selectedFruitTree.getSaplingId(), fruitTreeSaplingsCount)); @@ -484,8 +543,8 @@ private void bank(FarmTreeRunConfig config) { if (hardTreeSaplingsCount > 0) items.add(new FarmingItem(selectedHardTree.getSaplingId(), hardTreeSaplingsCount)); - if (config.protectTrees()) - items.add(new FarmingItem(selectedTree.getPaymentId(), selectedTree.getPaymentAmount() * treeSaplingsCount, true)); + if (config.protectTrees() && regularTreeSaplingsCount > 0) + items.add(new FarmingItem(selectedTree.getPaymentId(), selectedTree.getPaymentAmount() * regularTreeSaplingsCount, true)); if (config.protectHardTrees()) items.add(new FarmingItem(selectedHardTree.getPaymentId(), selectedHardTree.getPaymentAmount() * hardTreeSaplingsCount, true)); @@ -523,6 +582,15 @@ private void bank(FarmTreeRunConfig config) { // TODO: Need to handle what happens if a required item does not exist + // Merge entries with the same itemId + noted flag so withdrawal doesn't under-count + Map merged = new LinkedHashMap<>(); + for (FarmingItem item : items) { + long key = ((long) item.getItemId() << 1) | (item.isNoted() ? 1 : 0); + merged.merge(key, item, (a, b) -> + new FarmingItem(a.getItemId(), a.getQuantity() + b.getQuantity(), a.isNoted(), a.isOptional())); + } + items = new ArrayList<>(merged.values()); + // Deposit only what we don't need: keep desired ids and their noted variants Set keepIds = new HashSet<>(); for (FarmingItem item : items) { @@ -688,7 +756,7 @@ private void handleNotingFruit(Patch patch) { ItemID.DRAGONFRUIT }; - if (!Rs2Inventory.hasItem(fruitIds) || Rs2Player.isAnimating()) return; + if (!Rs2Inventory.hasItem(fruitIds)) return; // Iterate through the fruit IDs for (int fruitId : fruitIds) { @@ -717,9 +785,8 @@ private boolean handlePayment(FarmTreeRunConfig config, Patch patch, PaymentKind if (isHardTreePatch(patch) && !isPatchEmpty(patch) && !shouldProtectHardTree(config) && action != PaymentKind.CLEAR) return true; - Rs2NpcModel treeGardener = null; - treeGardener = Rs2Npc.getNearestNpcWithAction("Pay"); - Rs2Npc.interact(treeGardener, "Pay"); + Rs2NpcModel treeGardener = Rs2Npc.getNearestNpcWithAction("Pay"); + if (treeGardener != null) Rs2Npc.interact(treeGardener, "Pay"); if (treeGardener == null) { handleExoticGardeners(); @@ -734,14 +801,21 @@ private boolean handlePayment(FarmTreeRunConfig config, Patch patch, PaymentKind sleep(500, 850); if (Rs2Dialogue.hasSelectAnOption()) { + if (action == PaymentKind.PROTECT) { + if (!Rs2Dialogue.clickOption("don't ask")) { + Rs2Dialogue.clickOption("Yes"); + } + sleep(500, 1500); + Rs2Dialogue.clickContinue(); + sleepUntil(() -> !Rs2Dialogue.isInDialogue(), 6000); + return true; + } Rs2Dialogue.clickOption("Yes"); sleepUntil(() -> isPatchEmpty(patch), 6000); if (isPatchEmpty(patch)) { return true; } - shutdown(); - - System.out.println("Failed gardener money payment."); + System.out.println("Failed gardener clear payment."); return false; } else { System.out.println("Failed gardener payment."); @@ -757,11 +831,21 @@ private boolean handlePlantingTree(GameObject treePatch, Patch patch, FarmTreeRu int saplingToUse = getSaplingToUse(patch, config); - Microbot.log("Reached here"); if (useCompostOnPatch(config, patch)) { - Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); - Rs2Player.waitForXpDrop(Skill.FARMING, 2000); - sleep(550, 2200); + boolean hasCompost = Rs2Inventory.hasItem(compostItemId); + if (!hasCompost && !config.compostType().isReusable()) { + hasCompost = withdrawCompostFromLeprechaun(config.compostType()); + if (!hasCompost) { + Microbot.showMessage("Tool Leprechaun has no " + config.compostType() + ". Store compost with the leprechaun before starting."); + shutdown(); + return false; + } + } + if (hasCompost) { + Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); + Rs2Player.waitForXpDrop(Skill.FARMING, 2000); + sleep(550, 2200); + } } sleep(250, 1000); @@ -796,39 +880,23 @@ private void handleCheckHealth(GameObject treePatch) { private void handleRakeAction(GameObject treePatch) { System.out.println("Raking the patch..."); - // Rake the patch Rs2GameObject.interact(treePatch, "rake"); - - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); - - // Drop the weeds (assuming weeds are added to the inventory) - if (!Rs2Player.isMoving() && - !Rs2Player.isAnimating() && - !Rs2Player.isInteracting() && !Rs2Player.isMoving()) { - System.out.println("Dropping weeds..."); + Rs2Player.waitForXpDrop(Skill.FARMING, 10000); + if (Rs2Inventory.hasItem(ItemID.WEEDS)) { Rs2Inventory.dropAll(ItemID.WEEDS); - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); + sleepUntil(() -> !Rs2Inventory.hasItem(ItemID.WEEDS), 5000); } } private void handleClearAction(GameObject treePatch) { System.out.println("Clearing dead tree..."); - // Try to interact with the patch using the "clear" action boolean interactionSuccess = Rs2GameObject.interact(treePatch, "clear"); - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating()); - if (!interactionSuccess) { System.out.println("Failed to interact with the tree patch to clear it."); return; } - - // Wait for the clearing animation to finish - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && Rs2Player.isInteracting() && Rs2Player.isMoving()); + Rs2Player.waitForXpDrop(Skill.FARMING, 10000); } private void equipGraceful() { @@ -878,8 +946,12 @@ private boolean isFruitTreePatch(Patch patch) { return patch.kind == TreeKind.FRUIT_TREE; } + private boolean withdrawCompostFromLeprechaun(CompostType compostType) { + return Rs2Leprechaun.withdrawCompost(compostType.getItemId()); + } + private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { - if (!config.useCompost() || compostItemId == null) + if (config.compostType() == CompostType.NONE || compostItemId == null) return false; if (!config.protectTrees() && patch.kind == TreeKind.TREE) @@ -892,7 +964,7 @@ private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { } private List getSelectedTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible tree patches + if (!config.enableTrees()) return Collections.emptyList(); List allTreePatches = List.of( config::faladorTreePatch, config::gnomeStrongholdTreePatch, @@ -900,7 +972,8 @@ private List getSelectedTreePatches(FarmTreeRunConfig config) { config::taverleyTreePatch, config::varrockTreePatch, config::farmingGuildTreePatch, - config::auburnTreePatch + config::auburnTreePatch, + config::priffddinasCrystalTreePatch ); // Filter the patches to include only those that return true @@ -910,7 +983,7 @@ private List getSelectedTreePatches(FarmTreeRunConfig config) { } private List getSelectedHardTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible tree patches + if (!config.enableHardTrees()) return Collections.emptyList(); List allHardTreePatches = List.of( config::fossilTreePatch, config::fossilTreePatch, @@ -925,7 +998,7 @@ private List getSelectedHardTreePatches(FarmTreeRunConfig confi } private List getSelectedFruitTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible fruit tree patches + if (!config.enableFruitTrees()) return Collections.emptyList(); List allFruitTreePatches = List.of( config::brimhavenFruitTreePatch, config::catherbyFruitTreePatch, @@ -949,7 +1022,7 @@ private List getSelectedFruitTreePatches(FarmTreeRunConfig conf * @return true if configured by player, else false */ private boolean isCompostEnabled(FarmTreeRunConfig config) { - if (!config.useCompost()) + if (config.compostType() == CompostType.NONE) return false; if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) @@ -987,6 +1060,9 @@ private static int getSaplingToUse(Patch patch, FarmTreeRunConfig config) { if (patch == Patch.FOSSIL_TREE_PATCH_A || patch == Patch.FOSSIL_TREE_PATCH_B || patch == Patch.FOSSIL_TREE_PATCH_C ) { return config.selectedHardTree().getSaplingId(); + } else if (patch == Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH) { + return ItemID.CRYSTAL_SAPLING; + } else return patch.kind == TreeKind.TREE ? config.selectedTree().getSaplingId() : config.selectedFruitTree().getSaplingId(); @@ -1001,17 +1077,13 @@ private static int getSaplingToUse(Patch patch, FarmTreeRunConfig config) { * @return true if gardener interaction successful, else false */ private void handleExoticGardeners() { - // Nikkie: Farming guild fruit tree gardener - Rs2NpcModel nikkie = Rs2Npc.getNpc("Nikkie"); + var nikkie = Microbot.getRs2NpcCache().query().withName("Nikkie").nearestOnClientThread(); - // Rosie: Farming guild tree patch gardener - Rs2NpcModel rosie = Rs2Npc.getNpc("Rosie"); + var rosie = Microbot.getRs2NpcCache().query().withName("Rosie").nearestOnClientThread(); - Rs2NpcModel npcToInteract = null; String paymentAction = ""; + net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel npcToInteract = null; - // Rosie and Nikkie are close together. - // We need to check their distance to make sure we got the correct gardener. if (rosie == null && nikkie == null) { Microbot.log("Gardeners in farming guild not found. Report this bug."); shutdown(); @@ -1024,7 +1096,7 @@ private void handleExoticGardeners() { paymentAction = "Pay (tree patch)"; } - Rs2Npc.interact(npcToInteract, paymentAction); + if (npcToInteract != null) npcToInteract.click(paymentAction); } @Override @@ -1032,6 +1104,7 @@ public void shutdown() { if(isRunning()) { items.clear(); super.shutdown(); + Microbot.stopPlugin(plugin); } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java new file mode 100644 index 0000000000..21114bbe5f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.farmtreerun.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.gameval.ItemID; + +@Getter +@RequiredArgsConstructor +public enum CompostType { + NONE("None", -1, false), + COMPOST("Compost", ItemID.BUCKET_COMPOST, false), + SUPERCOMPOST("Supercompost", ItemID.BUCKET_SUPERCOMPOST, false), + ULTRACOMPOST("Ultracompost", ItemID.BUCKET_ULTRACOMPOST, false), + BOTTOMLESS_BUCKET("Bottomless bucket", ItemID.BOTTOMLESS_COMPOST_BUCKET_FILLED, true); + + private final String name; + private final int itemId; + private final boolean reusable; + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java index 4e5b084968..e9cfc0c11a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java @@ -49,6 +49,8 @@ public enum FarmTreeRunState { HANDLE_KASTORI_FRUIT_TREE_PATCH, + HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH, + HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH, FINISHED diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java index cb183d0213..25d8321c61 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java @@ -10,7 +10,7 @@ @RequiredArgsConstructor public enum HardTreeEnums { TEAK("Teak sapling", ItemID.PLANTPOT_TEAK_SAPLING, ItemID.LIMPWURT_ROOT, 15,75), - MAHOGANY("Mahogany sapling", ItemID.PLANTPOT_MAHOGANY_SAPLING, ItemID.LIMPWURT_ROOT, 15,75); + MAHOGANY("Mahogany sapling", ItemID.PLANTPOT_MAHOGANY_SAPLING, ItemID.YANILLIAN_HOPS, 25,75); private final String name; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java index c8f0c18ddd..da7fe939d6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java @@ -25,7 +25,7 @@ ) @Slf4j public class FishingTrawlerPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.0.1"; @Inject private FishingTrawlerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java index c14668ecea..a60f2f9371 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.fishingtrawler; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; @@ -10,10 +8,11 @@ import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -46,11 +45,11 @@ public boolean run(FishingTrawlerConfig config) { return; } - if (Rs2GameObject.exists(OBJECT_TRAWLERNET)) { + if (Microbot.getRs2TileObjectCache().query().withId(OBJECT_TRAWLERNET).nearest() != null) { if (wasInsideBoat) { Microbot.log("Looting Rewards"); Microbot.status = "Looting Rewards"; - Rs2GameObject.interact(OBJECT_TRAWLERNET, "inspect"); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_TRAWLERNET, "inspect"); Rs2Player.waitForWalking(); //check for visible trawler widget sleepUntil(() -> Rs2Widget.isWidgetVisible(367, 19), 5000); @@ -70,7 +69,7 @@ public boolean run(FishingTrawlerConfig config) { Microbot.log("Crossing Gangplank"); Rs2Player.waitForWalking(10000); } - } else if (Rs2GameObject.exists(OBJECT_DAISIES) && Rs2Player.getWorldLocation().getPlane() == 0) { + } else if (Microbot.getRs2TileObjectCache().query().withId(OBJECT_DAISIES).nearest() != null && Rs2Player.getWorldLocation().getPlane() == 0) { Microbot.log("Washed up — heading back"); Rs2Walker.walkTo(new WorldPoint(2676, 3170, 0)); } @@ -97,7 +96,7 @@ public boolean run(FishingTrawlerConfig config) { log.debug("Contribution widget missing or malformed."); } - if (Rs2Player.getWorldLocation().getPlane() == 0 && Rs2GameObject.exists(OBJECT_SHIPSLADDER)) { + if (Rs2Player.getWorldLocation().getPlane() == 0 && Microbot.getRs2TileObjectCache().query().withId(OBJECT_SHIPSLADDER).nearest() != null) { Microbot.log("In minigame — heading up ladder"); wasInsideBoat = true; Rs2GameObject.interact(new WorldPoint(1884, 4826, 0), "Climb-up"); @@ -111,22 +110,22 @@ public boolean run(FishingTrawlerConfig config) { if (!Rs2Player.isInteracting() && !Rs2Player.isMoving()) { log.debug("Tentacle phase"); - NPC tentacleNpc = Rs2Npc.getNpc("Enormous Tentacle"); + Rs2NpcModel tentacleNpc = Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").nearestOnClientThread(); if (tentacleNpc != null) { if (!tentacle) { Microbot.log("Tentacle found, chopping it down"); - Rs2Camera.turnTo(tentacleNpc); + Rs2Camera.turnTo(tentacleNpc.getNpc()); } - sleepUntil(() -> tentacleNpc.getAnimation() == 8953, 10000); - Rs2Npc.interact("Enormous Tentacle", "Chop"); + sleepUntil(() -> tentacleNpc.getNpc().getAnimation() == 8953, 10000); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop")); sleepUntilTick(2); - if (!Rs2Player.isInteracting()) Rs2Npc.interact("Enormous Tentacle", "Chop"); + if (!Rs2Player.isInteracting()) Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop")); tentacle = true; wasInsideBoat = true; sleepUntil(() -> !Rs2Player.isInteracting()); } else if (tentacle) { - GameObject ladderObject = Rs2GameObject.getGameObject(OBJECT_TENTACLE_LADDER); + Rs2TileObjectModel ladderObject = Microbot.getRs2TileObjectCache().query().withId(OBJECT_TENTACLE_LADDER).nearest(); if (ladderObject != null) { WorldPoint current = Rs2Player.getWorldLocation(); WorldPoint ladder = ladderObject.getWorldLocation(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java index 4955ed7e2b..1cc0762474 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java @@ -27,7 +27,7 @@ @Slf4j public class FletchingPlugin extends Plugin { - public static final String version = "1.6.3"; + public static final String version = "1.7.0"; @Inject private FletchingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java index ab1df412ef..e5233eb7e7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java @@ -35,6 +35,9 @@ public class FletchingScript extends Script { // The fletching interface widget group ID private static final int FLETCHING_WIDGET_GROUP_ID = 17694736; + private static final String FLETCHING_KNIFE = "fletching knife"; + private static final String KNIFE = "knife"; + ProgressiveFletchingModel model = new ProgressiveFletchingModel(); String primaryItemToFletch = ""; @@ -68,10 +71,10 @@ public void run(FletchingConfig config) { boolean hasRequirementsToFletch; boolean hasRequirementsToBank; - primaryItemToFletch = fletchingMode.getItemName(); + primaryItemToFletch = usesKnife() ? getPreferredKnife() : fletchingMode.getItemName(); if (fletchingMode == FletchingMode.PROGRESSIVE) { - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); + secondaryItemToFletch = model.getFletchingMaterial().getLogItemName(); hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) && Rs2Inventory.hasItemAmount(secondaryItemToFletch, model.getFletchingItem().getAmountRequired()); hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) @@ -84,7 +87,7 @@ public void run(FletchingConfig config) { } else { secondaryItemToFletch = fletchingMode == FletchingMode.STRUNG ? config.fletchingMaterial().getName() + " " + config.fletchingItem().getContainsInventoryName() + " (u)" - : (config.fletchingMaterial().getName() + " logs").trim(); + : config.fletchingMaterial().getLogItemName(); hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) && Rs2Inventory.hasItemAmount(secondaryItemToFletch, config.fletchingItem().getAmountRequired()); hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) @@ -105,7 +108,11 @@ public void run(FletchingConfig config) { } private void bankItems(FletchingConfig config) { - Rs2Bank.openBank(); + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.openBank()) { + return; // Bank didn't open, retry next iteration + } + } // Deposit items based on the fletching mode switch (fletchingMode) { @@ -115,7 +122,7 @@ private void bankItems(FletchingConfig config) { case PROGRESSIVE: Rs2Bank.depositAll(model.getFletchingItem().getContainsInventoryName()); calculateItemToFletch(); - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); + secondaryItemToFletch = model.getFletchingMaterial().getLogItemName(); break; case PROGRESSIVE_STRUNG: Rs2Bank.depositAll(); @@ -130,7 +137,9 @@ private void bankItems(FletchingConfig config) { } // Check if the primary item is available - if (!Rs2Bank.hasItem(primaryItemToFletch) && !Rs2Inventory.hasItem(primaryItemToFletch)) { + boolean hasPrimaryInBank = usesKnife() ? bankHasAnyKnife() : Rs2Bank.hasItem(primaryItemToFletch); + boolean hasPrimaryInInventory = usesKnife() ? hasAnyKnife() : Rs2Inventory.hasItem(primaryItemToFletch); + if (!hasPrimaryInBank && !hasPrimaryInInventory) { Rs2Bank.closeBank(); Microbot.status = "[Shutting down] - Reason: " + primaryItemToFletch + " not found in the bank."; Microbot.showMessage(Microbot.status); @@ -139,12 +148,12 @@ private void bankItems(FletchingConfig config) { } // Ensure the inventory isn't full without the primary item - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { + if (!hasPrimaryInInventory) { Rs2Bank.depositAll(); } // Withdraw the primary item if not already in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { + if (!hasPrimaryInInventory) { Rs2Bank.withdrawX(primaryItemToFletch, fletchingMode.getAmount(), true); } @@ -181,7 +190,8 @@ private void bankItems(FletchingConfig config) { } // Final check to ensure both items are in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch) || !Rs2Inventory.hasItem(secondaryItemToFletch)) { + boolean hasPrimaryFinal = usesKnife() ? hasAnyKnife() : Rs2Inventory.hasItem(primaryItemToFletch); + if (!hasPrimaryFinal || !Rs2Inventory.hasItem(secondaryItemToFletch)) { Microbot.log("waiting for inventory changes."); Rs2Inventory.waitForInventoryChanges(5000); } @@ -192,7 +202,8 @@ private void bankItems(FletchingConfig config) { private void fletch(FletchingConfig config) { - Rs2Inventory.combineClosest(primaryItemToFletch, secondaryItemToFletch); + String itemToUse = usesKnife() ? getKnifeInInventory() : primaryItemToFletch; + Rs2Inventory.combineClosest(itemToUse, secondaryItemToFletch); sleepUntil(() -> Rs2Widget.getWidget(FLETCHING_WIDGET_GROUP_ID) != null, 5000); char option; if (fletchingMode == FletchingMode.PROGRESSIVE || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { @@ -204,10 +215,11 @@ private void fletch(FletchingConfig config) { Rs2Keyboard.keyPress(option); } + Rs2Bank.preHover(); + sleepUntil(() -> !Rs2Inventory.hasItem(secondaryItemToFletch), 60000); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); - Rs2Bank.preHover(); } private boolean configChecks(FletchingConfig config) { @@ -219,6 +231,34 @@ private boolean configChecks(FletchingConfig config) { return true; } + private String getPreferredKnife() { + if (Rs2Inventory.hasItem(FLETCHING_KNIFE) || Rs2Bank.hasItem(FLETCHING_KNIFE)) { + return FLETCHING_KNIFE; + } + return KNIFE; + } + + private boolean hasAnyKnife() { + return Rs2Inventory.hasItem(FLETCHING_KNIFE) || Rs2Inventory.hasItem(KNIFE); + } + + private String getKnifeInInventory() { + if (Rs2Inventory.hasItem(FLETCHING_KNIFE)) { + return FLETCHING_KNIFE; + } + return KNIFE; + } + + private boolean bankHasAnyKnife() { + return Rs2Bank.hasItem(FLETCHING_KNIFE) || Rs2Bank.hasItem(KNIFE); + } + + private boolean usesKnife() { + return fletchingMode == FletchingMode.UNSTRUNG + || fletchingMode == FletchingMode.UNSTRUNG_STRUNG + || fletchingMode == FletchingMode.PROGRESSIVE; + } + public void calculateItemToFletch() { int level = Microbot.getClient().getRealSkillLevel(Skill.FLETCHING); FletchingItem item = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java index e8f0be64e0..0e15b44504 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java @@ -18,6 +18,9 @@ public enum FletchingMaterial private final String name; + public String getLogItemName() { + return this == LOG ? "Logs" : name + " logs"; + } @Override public String toString() diff --git a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java index 9a24148558..b5ca60d8d2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java @@ -20,8 +20,8 @@ import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -48,7 +48,7 @@ ) @Slf4j public class FlippersChaserPlugin extends Plugin { - public static final String version = "1.0.1"; + public static final String version = "1.0.2"; @Inject private Client client; @@ -103,7 +103,8 @@ public void onNpcSpawned(NpcSpawned event) { NPC npc = event.getNpc(); if (npc.getName().equals("Mogre")) { inCombat = true; - clientThread.invoke(() -> attackNpc(new Rs2NpcModel(npc))); + var rs2Npc = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(npc)).nearest(); + if (rs2Npc != null) clientThread.invoke(() -> attackNpc(rs2Npc)); } } @@ -121,16 +122,16 @@ public void onNpcLootReceived(NpcLootReceived event) { private void useFishingExplosive() { Rs2NpcModel fishingSpot = findFishingSpot(); if (fishingSpot != null) { - Rs2Inventory.useItemOnNpc(ItemID.FISHING_EXPLOSIVE, fishingSpot); + Rs2Inventory.useItemOnNpc(ItemID.FISHING_EXPLOSIVE, fishingSpot.getNpc()); } } private Rs2NpcModel findFishingSpot() { - return Rs2Npc.getNpc("Ominous Fishing Spot"); + return Microbot.getRs2NpcCache().query().withName("Ominous Fishing Spot").nearestOnClientThread(); } private void attackNpc(Rs2NpcModel npc) { - Rs2Npc.interact(npc); + npc.click("Attack"); if (Rs2Player.hasPrayerPoints()) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java index 15e7eb9423..02fb09f7f9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java @@ -36,7 +36,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class RcPlugin extends Plugin { - public static final String version = "1.1.3"; + public static final String version = "1.1.4"; @Inject private RcConfig config; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java index b351f23352..9ae7dca815 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java @@ -18,7 +18,7 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; @@ -31,9 +31,7 @@ import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class RcScript extends Script { private final RcPlugin plugin; @@ -364,7 +362,7 @@ private void handleFeroxRunEnergy() { if (plugin.getMyWorldPoint().distanceTo(feroxPoolWp) < 5) { Microbot.log("Interacting with the Ferox pool"); - Rs2GameObject.interact(feroxPool, "Drink"); + Microbot.getRs2TileObjectCache().query().interact(feroxPool, "Drink"); } sleepUntil(() -> (!Rs2Player.isInteracting()) && !Rs2Player.isAnimating() && Rs2Player.getRunEnergy() > 90); sleepGaussian(1100, 200); @@ -396,15 +394,12 @@ private void handleArdyCloak() { sleepGaussian(900, 200); } - TileObject fairyRing = Rs2GameObject.getAll().stream() - .filter(Objects::nonNull) - .filter(obj -> obj.getLocalLocation().distanceTo(Microbot.getClient().getLocalPlayer().getLocalLocation()) < 5000) - .filter(obj -> { - ObjectComposition composition = Rs2GameObject.getObjectComposition(obj.getId()); - if (composition == null) return false; - return composition.getName().toLowerCase().contains("fairy"); + Rs2TileObjectModel fairyRing = Microbot.getRs2TileObjectCache().query() + .where(obj -> { + String name = obj.getName(); + return name != null && name.toLowerCase().contains("fairy"); }) - .findFirst().orElse(null); + .within(10).nearest(); if (plugin.getMyWorldPoint().distanceTo(monasteryFairyRing) < 7) { if (fairyRing == null) { @@ -413,7 +408,7 @@ private void handleArdyCloak() { return; } else { Microbot.log("Interacting with fairies"); - Rs2GameObject.interact(fairyRing, "Last-destination (DLS)"); + fairyRing.click("Last-destination (DLS)"); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing)); } } @@ -433,7 +428,7 @@ private void handleFarmingCape() { if (plugin.getMyWorldPoint().distanceTo(guildSpiritTreeLoc) > 10) { Rs2Walker.walkTo(guildSpiritTreeLoc); } else { - Rs2GameObject.interact(guildSpiritTree, "Travel"); + Microbot.getRs2TileObjectCache().query().interact(guildSpiritTree, "Travel"); sleepUntil(() -> Rs2Widget.isWidgetVisible(187, 3), 10000); sleepGaussian(1100, 200); @@ -453,12 +448,11 @@ private void handleFarmingCape() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { if (config.runeType() == RuneType.BLOOD) { @@ -485,9 +479,9 @@ private void handleWrathWalking() { sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion); sleepGaussian(600, 200); - GameObject statue = Rs2GameObject.get("Mythic Statue"); + Rs2TileObjectModel statue = Microbot.getRs2TileObjectCache().query().withName("Mythic Statue").nearestOnClientThread(); if (statue != null && !Rs2Player.isAnimating()) { - Rs2GameObject.interact(statue, "Teleport"); + statue.click("Teleport"); } if (plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion) { @@ -497,7 +491,7 @@ private void handleWrathWalking() { Microbot.log("Current position " + plugin.getMyWorldPoint()); if (plugin.getMyWorldPoint() == outsideWrathRuins) { - Rs2GameObject.interact(wrathRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(wrathRuins, "Enter"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == wrathAltarRegion); } } @@ -558,12 +552,11 @@ private void handleGoingHome() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { @@ -605,12 +598,11 @@ private void handleGoingHome() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { @@ -623,26 +615,21 @@ private void handleGoingHome() { } private void handlePohFairyRing() { - if (Rs2GameObject.findObjectById(ObjectID.POH_FAIRY_RING) != null) { - Rs2GameObject.interact(ObjectID.POH_FAIRY_RING, "Last-destination (DLS)"); + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.POH_FAIRY_RING).nearest() != null) { + Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_FAIRY_RING, "Last-destination (DLS)"); Microbot.log("Using fairy ring"); Rs2Player.waitForAnimation(1200); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing), 1200); state = State.WALKING_TO; } else { - List allGameObjects = Rs2GameObject.getAll().stream() - .filter(Objects::nonNull) - .filter(obj -> obj.getLocalLocation().distanceTo(Microbot.getClient().getLocalPlayer().getLocalLocation()) < 5000) - .collect(Collectors.toList()); - - TileObject pohTreeRing = allGameObjects.stream() - .filter(obj -> { - ObjectComposition composition = Rs2GameObject.getObjectComposition(obj.getId()); - return composition != null && composition.getName().toLowerCase().contains("spirit"); + Rs2TileObjectModel pohTreeRing = Microbot.getRs2TileObjectCache().query() + .where(obj -> { + String name = obj.getName(); + return name != null && name.toLowerCase().contains("spirit"); }) - .findFirst().orElse(null); + .within(10).nearest(); if (pohTreeRing != null) { - Rs2GameObject.interact(pohTreeRing, "Ring-last-destination (DLS)"); + pohTreeRing.click("Ring-last-destination (DLS)"); Microbot.log("Using fairy tree"); Rs2Player.waitForAnimation(); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing)); @@ -671,7 +658,7 @@ private void handleWalking() { Microbot.log("Current location after waiting: " + plugin.getMyWorldPoint()); if (plugin.getMyWorldPoint().equals(caveFairyRing)) { sleepGaussian(900, 200); - Rs2GameObject.interact(16308, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(16308, "Enter"); sleepUntil(() -> Rs2Player.getWorldLocation().equals(firstCaveExit), 1200); sleepGaussian(900, 200); } @@ -691,7 +678,7 @@ private void handleWalking() { sleepUntil(() -> plugin.getMyWorldPoint().equals(outsideBloodRuins74), 1200); } - TileObject ruins = Rs2GameObject.findObjectById(bloodRuins); + Rs2TileObjectModel ruins = Microbot.getRs2TileObjectCache().query().withId(bloodRuins).nearest(); if (plugin.getMyWorldPoint().equals(firstCaveExit) && Rs2Player.getRealSkillLevel(Skill.AGILITY) < 74) { Microbot.log("Walking to ruins: " + outsideBloodRuins73); @@ -713,10 +700,10 @@ private void handleCrafting() { } if (config.runeType() == RuneType.BLOOD) { - Rs2GameObject.interact(bloodRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(bloodRuins, "Enter"); sleepUntil(() -> !Rs2Player.isAnimating() && plugin.getMyWorldPoint().getRegionID() == bloodAltarRegion); sleepGaussian(700, 200); - Rs2GameObject.interact(bloodAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(bloodAltar, "Craft-rune"); Rs2Player.waitForXpDrop(Skill.RUNECRAFT); plugin.updateXpGained(); handleEmptyPouch(); @@ -733,7 +720,7 @@ private void handleCrafting() { if (config.runeType() == RuneType.WRATH) { Microbot.log("Entering wrath ruins"); - Rs2GameObject.interact(wrathRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(wrathRuins, "Enter"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == wrathAltarRegion); sleepGaussian(1100, 200); Microbot.log("Crafting runes"); @@ -759,10 +746,10 @@ private void handleEmptyPouch() { Rs2Inventory.waitForInventoryChanges(600); sleepGaussian(700, 200); if (config.runeType() == RuneType.BLOOD) { - Rs2GameObject.interact(bloodAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(bloodAltar, "Craft-rune"); } if (config.runeType() == RuneType.WRATH) { - Rs2GameObject.interact(wrathAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(wrathAltar, "Craft-rune"); } Rs2Player.waitForXpDrop(Skill.RUNECRAFT); plugin.updateXpGained(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java index 705a735ad1..916d4e15fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java @@ -23,7 +23,6 @@ import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; diff --git a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java index aae2331e23..06ca171f24 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java @@ -18,8 +18,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -177,9 +176,8 @@ public boolean run() { public void checkNPC() { TIME_NPC = now; - Tornado = Rs2Npc.getNpc(CG_TORNADO); - hunllef = Rs2Npc.getNpcs() - .filter(npc -> HUNLLEF_IDS.contains(npc.getId())) + Tornado = Microbot.getRs2NpcCache().query().withId(CG_TORNADO).nearest(); + hunllef = Microbot.getRs2NpcCache().query().where(npc -> HUNLLEF_IDS.contains(npc.getId())).toList().stream() .findFirst() .orElse(null); @@ -397,7 +395,7 @@ private void checkAttack(){ if (attackNeeded.compareAndSet(true, false)) { if (now - TIME_EAT_ATTEMPTED > (CD_EAT * 3)) { logVerbose("Attempting attack"); - Rs2Npc.interact(hunllef, "attack"); + hunllef.click("attack"); TIME_ATTACK = now; } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java index b6b5ba03e0..c10bb2ffeb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java @@ -17,7 +17,7 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; @@ -25,8 +25,7 @@ import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.security.Login; @@ -239,9 +238,9 @@ public void hopWorlds() /** * Retrieves the Mole Hill tile object by ID. */ - public TileObject getMoleHill() + public Rs2TileObjectModel getMoleHill() { - return Rs2GameObject.getTileObject(ObjectID.MOLE_HILL); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.MOLE_HILL).nearest(); } /** @@ -249,10 +248,10 @@ public TileObject getMoleHill() */ public void checkWorldOccupied() { - TileObject moleHill = getMoleHill(); + Rs2TileObjectModel moleHill = getMoleHill(); if (moleHill != null) { - Rs2GameObject.interact(moleHill, "Look-inside"); + moleHill.click("Look-inside"); Global.sleepUntilTrue(() -> checkedIfWorldOccupied, 200, 7000); } } @@ -262,7 +261,7 @@ public void checkWorldOccupied() */ public void goInsideMoleHill() { - TileObject moleHill = getMoleHill(); + Rs2TileObjectModel moleHill = getMoleHill(); if (moleHill != null) { if (Rs2Walker.walkTo(moleHill.getWorldLocation(), 0)) @@ -310,10 +309,13 @@ public static boolean isMoleDead() */ public Rs2NpcModel getMole() { - return Microbot.getClientThread() + NPC hintNpc = Microbot.getClientThread() .runOnClientThreadOptional(() -> Microbot.getClient().getHintArrowNpc()) - .map(Rs2NpcModel::new) .orElse(null); + if (hintNpc == null) return null; + return Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() == hintNpc) + .nearest(); } /** @@ -383,7 +385,7 @@ public void attackMole() sleep(600, 800); } - if (Rs2Npc.interact(mole, "Attack")) + if (mole.click("Attack")) { sleep(600, 800); } @@ -399,7 +401,7 @@ public void handlePrayer(GiantMoleConfig config) return; } - boolean underAttack = Rs2Npc.getNpcsForPlayer().findAny().isPresent() || Rs2Combat.inCombat(); + boolean underAttack = Microbot.getRs2NpcCache().query().where(Rs2NpcModel::isInteractingWithPlayer).count() > 0 || Rs2Combat.inCombat(); Rs2Prayer.toggleQuickPrayer(!isInFalador() && underAttack); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java index e5258580e5..3b013254bf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java @@ -28,7 +28,7 @@ @Slf4j public class GiantsFoundryPlugin extends Plugin { - public static final String version = "1.0.6"; + public static final String version = "1.0.7"; @Inject private GiantsFoundryConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java index 203a629ce1..4c8bffbd8c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.giantsfoundry; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameObject; import net.runelite.api.ObjectComposition; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; @@ -17,9 +16,9 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.concurrent.TimeUnit; @@ -104,7 +103,8 @@ public boolean hasCommission() { public void getCommission() { if (!hasCommission()) { GiantsFoundryState.reset(); - if (Rs2Npc.interact("kovac", "Commission")) + var kovac = Microbot.getRs2NpcCache().query().withName("kovac").nearestOnClientThread(); + if (kovac != null && kovac.click("Commission")) sleepUntil(this::hasCommission, 5000); } } @@ -119,7 +119,7 @@ public void selectMould() { if (hasSelectedMould()) return; - Rs2GameObject.interact(MOULD_JIG); + Microbot.getRs2TileObjectCache().query().withId(MOULD_JIG).interact(); sleepUntil(() -> Rs2Widget.findWidget("Forte", null) != null, 5000); @@ -204,7 +204,7 @@ private boolean ensureBarsInInventory() { private void addBarsWithWidget(String barName, SmithableBars key) { if (!Rs2Inventory.hasItem(barName) || canPour()) return; - Rs2GameObject.interact(CRUCIBLE, "Fill"); + Microbot.getRs2TileObjectCache().query().interact(CRUCIBLE, "Fill"); sleepUntil(() -> Rs2Widget.findWidget("What metal would you like to add?", null) != null, 5000); Rs2Keyboard.keyPress(getKeyFromBar(key)); sleepUntil(() -> !Rs2Inventory.hasItem(barName), 5000); @@ -213,7 +213,7 @@ private void addBarsWithWidget(String barName, SmithableBars key) { private void addBarsWithInventoryUse(String barName) { if (!Rs2Inventory.hasItem(barName) || canPour()) return; Rs2Inventory.use(barName); - Rs2GameObject.interact(CRUCIBLE); + Microbot.getRs2TileObjectCache().query().withId(CRUCIBLE).interact(); sleepUntil(() -> Rs2Widget.findWidget("How many would you like to add?", null) != null, 5000); sleep(600, 1200); @@ -224,7 +224,7 @@ private void addBarsWithInventoryUse(String barName) { } private void pourCrucible() { - Rs2GameObject.interact(CRUCIBLE, "Pour"); + Microbot.getRs2TileObjectCache().query().interact(CRUCIBLE, "Pour"); sleep(5000); sleepUntil(() -> !canPour(), 10000); } @@ -249,7 +249,7 @@ public boolean canPickupMould() { public void pickupMould() { if (!canPickupMould()) return; if (Rs2Inventory.isEmpty() && GiantsFoundryState.getCurrentStage() == null) { - Rs2GameObject.interact(MOULD_JIG, "Pick-up"); + Microbot.getRs2TileObjectCache().query().interact(MOULD_JIG, "Pick-up"); sleepUntil(() -> !canPickupMould(), 5000); } } @@ -293,7 +293,7 @@ public void handleGameLoop() { boolean isAtLavaTile = Rs2Player.getWorldLocation().equals(new WorldPoint(3371, 11497, 0)) || Rs2Player.getWorldLocation().equals(new WorldPoint(3371, 11498, 0)); if (!doAction && isAtLavaTile) return; - Rs2GameObject.interact(LAVA_POOL, "Heat-preform"); + Microbot.getRs2TileObjectCache().query().interact(LAVA_POOL, "Heat-preform"); GiantsFoundryState.heatingCoolingState.stop(); GiantsFoundryState.heatingCoolingState.setup(false, true, "heats"); GiantsFoundryState.heatingCoolingState.start(GiantsFoundryState.getHeatAmount()); @@ -302,7 +302,7 @@ public void handleGameLoop() { case COOLING_DOWN: boolean isAtWaterFallTile = Rs2Player.getWorldLocation().equals(new WorldPoint(3360, 11489, 0)); if (!doAction && isAtWaterFallTile) return; - Rs2GameObject.interact(WATERFALL, "Cool-preform"); + Microbot.getRs2TileObjectCache().query().interact(WATERFALL, "Cool-preform"); GiantsFoundryState.heatingCoolingState.stop(); GiantsFoundryState.heatingCoolingState.setup(false, false, "cools"); GiantsFoundryState.heatingCoolingState.start(GiantsFoundryState.getHeatAmount()); @@ -324,14 +324,14 @@ public void handleGameLoop() { public void craftWeapon() { Stage stage = GiantsFoundryState.getCurrentStage(); if (stage == null) return; - GameObject obj = GiantsFoundryState.getStageObject(stage); + Rs2TileObjectModel obj = GiantsFoundryState.getStageObject(stage); if (obj == null) return; - Rs2GameObject.interact(obj); + obj.click(); Rs2Player.waitForAnimation(); } private void handIn() { - Rs2Npc.interact("kovac", "Hand-in"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("kovac").interact("Hand-in")); } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java index 70c6e83739..e33fe7a168 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java @@ -2,12 +2,11 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.GameObject; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.giantsfoundry.enums.Heat; import net.runelite.client.plugins.microbot.giantsfoundry.enums.Stage; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.util.ArrayList; @@ -124,14 +123,14 @@ public static List getStages() { return stages; } - public static GameObject getStageObject(Stage stage) { + public static Rs2TileObjectModel getStageObject(Stage stage) { switch (stage) { case TRIP_HAMMER: - return Rs2GameObject.getGameObject("trip hammer"); + return Microbot.getRs2TileObjectCache().query().withName("trip hammer").nearestOnClientThread(); case GRINDSTONE: - return Rs2GameObject.getGameObject("grindstone"); + return Microbot.getRs2TileObjectCache().query().withName("grindstone").nearestOnClientThread(); case POLISHING_WHEEL: - return Rs2GameObject.getGameObject("polishing wheel"); + return Microbot.getRs2TileObjectCache().query().withName("polishing wheel").nearestOnClientThread(); } return null; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java index 2b5af7c259..183551e99e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class GildedAltarPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.0.1"; @Inject private GildedAltarConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java index e11b7b86f0..dff8ba4b6b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java @@ -1,15 +1,13 @@ package net.runelite.client.plugins.microbot.gildedaltar; import net.runelite.api.ObjectID; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -37,7 +35,7 @@ public class GildedAltarScript extends Script { public static GildedAltarPlayerState state = GildedAltarPlayerState.IDLE; private boolean inHouse() { - return Rs2Npc.getNpc("Phials") == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread() == null; } private boolean hasUnNotedBones() { @@ -113,12 +111,12 @@ public void leaveHouse() { // We should only rely on using the settings menu if the portal is several rooms away from the portal. Bringing up 3 different interfaces when we can see the portal on screen is unnecessary. if(usePortal) { - TileObject portalObject = Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT); + Rs2TileObjectModel portalObject = Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest(); if (portalObject == null) { System.out.println("Not in house, HOUSE_PORTAL_OBJECT not found."); return; } - Rs2GameObject.interact(portalObject); + portalObject.click(); Rs2Player.waitForWalking(); return; } @@ -157,7 +155,7 @@ public void unnoteBones() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use("bones"); } else { - Rs2Npc.interact("Phials", "Use"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); Rs2Player.waitForWalking(); } } else if (Microbot.getClient().getWidget(14352385) != null) { @@ -169,7 +167,7 @@ public void unnoteBones() { private void enterHouse() { // If we've already visited a house this session, use 'Visit-Last' on advertisement board if (visitedOnce) { - Rs2GameObject.interact(ObjectID.HOUSE_ADVERTISEMENT, "Visit-Last"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HOUSE_ADVERTISEMENT, "Visit-Last"); sleep(2400, 3000); return; } @@ -177,7 +175,7 @@ private void enterHouse() { boolean isAdvertisementWidgetOpen = Rs2Widget.isWidgetVisible(3407875); if (!isAdvertisementWidgetOpen) { - Rs2GameObject.interact(ObjectID.HOUSE_ADVERTISEMENT, "View"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HOUSE_ADVERTISEMENT, "View"); sleep(1200, 1800); } @@ -244,7 +242,7 @@ public void bonesOnAltar() { } - TileObject altar = Rs2GameObject.getGameObject("Altar", true); + Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withName("Altar").nearestOnClientThread(); if (altar != null) { Rs2Inventory.useUnNotedItemOnObject("bones", altar.getId()); Rs2Player.waitForAnimation(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java b/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java index d8cd7b5fc6..756b88579b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -70,7 +69,7 @@ public boolean run(GabulhasGoldRushConfig config) { break; case USING_BARS: int currentXP = Microbot.getClient().getSkillExperience(Skill.SMITHING); - Rs2GameObject.interact(9100, "Put-ore-on"); + Microbot.getRs2TileObjectCache().query().withId(9100).interact("Put-ore-on"); while (Rs2Inventory.contains("Gold ore")) { sleep(100); } @@ -84,7 +83,7 @@ public boolean run(GabulhasGoldRushConfig config) { break; case RETRIEVING_BARS: Rs2Inventory.wield("Ice gloves"); - Rs2GameObject.interact(9092, "Take"); + Microbot.getRs2TileObjectCache().query().withId(9092).interact("Take"); Rs2Keyboard.keyPress(' '); while (!Rs2Inventory.contains("Gold bar")) { Rs2Keyboard.keyPress(32); diff --git a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java index 161a4c59a8..7f552ea253 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java @@ -41,7 +41,7 @@ ) @Slf4j public class GotrPlugin extends Plugin { - public static final String version = "1.5.1"; + public static final String version = "1.5.4"; @Inject private GotrConfig config; @@ -51,6 +51,9 @@ GotrConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(GotrConfig.class); } + @Inject + private ConfigManager configManager; + @Inject private OverlayManager overlayManager; @Inject @@ -71,6 +74,10 @@ public GotrScript getScript() { @Override protected void startUp() throws AWTException { + if (config.maxFragmentAmount() == 0) { + configManager.setConfiguration("gotr", "maxFragmentAmount", 100); + } + if (overlayManager != null) { overlayManager.add(pouchOverlay); overlayManager.add(gotrOverlay); diff --git a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java index 7fe8d85c8a..c36c592c6c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java @@ -21,8 +21,9 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -242,7 +243,7 @@ private boolean waitingForGameToStart(int timeToStart) { // Return to large mine if we were there before if (!isInLargeMine() && shouldMineGuardianRemains) { if (Rs2Walker.walkTo(new WorldPoint(3632, 9503, 0), 20)) { - Rs2GameObject.interact(ObjectID.RUBBLE_43724); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43724); return true; } } @@ -262,24 +263,26 @@ private boolean repairCells() { Rs2ItemModel cell = Rs2Inventory.get(CellType.PoweredCellList().stream().mapToInt(i -> i).toArray()); if (cell != null && isInMainRegion() && isInMiniGame() && !shouldMineGuardianRemains && !isInLargeMine() && !isInHugeMine()) { int cellTier = CellType.GetCellTier(cell.getId()); - List shieldCellIds = Rs2GameObject.getObjectIdsByName("cell_tile"); + List shieldCells = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getName() != null && o.getName().toLowerCase().contains("cell_tile")) + .toListOnClientThread(); if (Rs2Inventory.hasItemAmount(GUARDIAN_ESSENCE, 10)) { - for (int shieldCellId : shieldCellIds) { - TileObject shieldCell = Rs2GameObject.getTileObject(shieldCellId); - if (shieldCell == null) continue; + for (Rs2TileObjectModel shieldCell : shieldCells) { if (CellType.GetShieldTier(shieldCell.getId()) < cellTier) { Microbot.log("Upgrading power cell at " + shieldCell.getWorldLocation()); - Rs2GameObject.interact(shieldCell, "Place-cell"); + shieldCell.click("Place-cell"); sleepUntil(() -> !Rs2Player.isMoving()); return true; } } } - shieldCellIds = shieldCellIds.stream().filter(id -> id != ObjectID.CELL_TILE_BROKEN).collect(Collectors.toList()); - int interactedObjectId = Rs2GameObject.interact(shieldCellIds); - if (interactedObjectId != -1) { - log("Using cell with id " + interactedObjectId); + Rs2TileObjectModel cellToUse = shieldCells.stream() + .filter(o -> o.getId() != ObjectID.CELL_TILE_BROKEN) + .findFirst().orElse(null); + if (cellToUse != null) { + cellToUse.click(); + log("Using cell with id " + cellToUse.getId()); sleep(Rs2Random.randomGaussian(1000, 300)); sleepUntil(() -> !Rs2Player.isMoving()); } @@ -291,7 +294,7 @@ private boolean repairCells() { private boolean powerUpGreatGuardian() { if (Rs2Inventory.hasItem("guardian stone") && !shouldMineGuardianRemains && !isInLargeMine() && !isInHugeMine()) { state = GotrState.POWERING_UP; - Rs2Npc.interact("The great guardian", "power-up"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("The great guardian").interact("power-up")); log("Powering up the great guardian..."); sleepUntil(Rs2Player::isAnimating); sleep(Rs2Random.randomGaussian(Rs2Random.between(1000, 2000), Rs2Random.between(100, 300))); @@ -311,7 +314,7 @@ private void takeUnchargedCells() { } } - Rs2GameObject.interact(ObjectID.UNCHARGED_CELLS_43732, "Take-10"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.UNCHARGED_CELLS_43732, "Take-10"); log("Taking uncharged cells..."); Rs2Player.waitForAnimation(); } @@ -322,7 +325,7 @@ private boolean usePortal() { if (leaveLargeMine()) return true; Rs2Walker.walkFastCanvas(Microbot.getClient().getHintArrowPoint()); sleepUntil(Rs2Player::isMoving); - Rs2GameObject.interact(Microbot.getClient().getHintArrowPoint()); + Microbot.getRs2TileObjectCache().query().within(Microbot.getClient().getHintArrowPoint(), 0).interact(); log("Found a portal spawn...interacting with it..."); Rs2Player.waitForWalking(); sleepUntil(() -> isInHugeMine()); @@ -335,7 +338,7 @@ private boolean usePortal() { private boolean depositRunesIntoPool() { if (config.shouldDepositRunes() && Rs2Inventory.hasItem(runeIds.stream().mapToInt(i -> i).toArray()) && !isInLargeMine() && !isInHugeMine() && !Rs2Inventory.isFull() && !optimizedEssenceLoop) { if (Rs2Player.isMoving()) return true; - if (Rs2GameObject.interact(ObjectID.DEPOSIT_POOL)) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.DEPOSIT_POOL)) { log("Deposit runes into pool..."); sleep(600, 2400); } @@ -359,7 +362,7 @@ private boolean enterAltar() { } private boolean craftGuardianEssences() { - if (Rs2GameObject.interact(ObjectID.WORKBENCH_43754)) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.WORKBENCH_43754)) { state = GotrState.CRAFT_GUARDIAN_ESSENCE; sleep(Rs2Random.randomGaussian(Rs2Random.between(600, 900), Rs2Random.between(150, 300))); log("Crafting guardian essences..."); @@ -370,7 +373,7 @@ private boolean craftGuardianEssences() { private boolean leaveLargeMine() { if (isInLargeMine()) { - Rs2GameObject.interact(ObjectID.RUBBLE_43726); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43726); Rs2Player.waitForAnimation(); log("Leaving large mine..."); state = GotrState.LEAVING_LARGE_MINE; @@ -402,7 +405,7 @@ private boolean isOutOfFragments() { private boolean craftRunes() { if (!isInMainRegion() && isInMiniGame()) { - TileObject rcAltar = findRcAltar(); + Rs2TileObjectModel rcAltar = findRcAltar(); if (rcAltar != null) { if (Rs2Player.isMoving()) return true; if (Rs2Inventory.anyPouchFull() && !Rs2Inventory.isFull()) { @@ -413,13 +416,13 @@ private boolean craftRunes() { if (Rs2Inventory.hasItem(GUARDIAN_ESSENCE)) { state = GotrState.CRAFTING_RUNES; optimizedEssenceLoop = false; - Rs2GameObject.interact(rcAltar.getId()); + Microbot.getRs2TileObjectCache().query().interact(rcAltar.getId()); log("Crafting runes on altar " + rcAltar.getId()); sleep(Rs2Random.randomGaussian(Rs2Random.between(1000, 1500), 300)); } else if (!Rs2Player.isMoving()) { state = GotrState.LEAVING_ALTAR; - TileObject rcPortal = findPortalToLeaveAltar(); - if (Rs2GameObject.interact(rcPortal.getId())) { + Rs2TileObjectModel rcPortal = findPortalToLeaveAltar(); + if (Microbot.getRs2TileObjectCache().query().interact(rcPortal.getId())) { log("Leaving the altar..."); sleepUntilTrue(GotrScript::isInMainRegion,100,10000); sleep(Rs2Random.randomGaussian(750, 150)); @@ -433,8 +436,8 @@ private boolean craftRunes() { private static boolean waitForMinigameToStart() { if (!isInMainRegion()) { - TileObject rcPortal = findPortalToLeaveAltar(); - if (rcPortal != null && Rs2GameObject.interact(rcPortal.getId())) { + Rs2TileObjectModel rcPortal = findPortalToLeaveAltar(); + if (rcPortal != null && Microbot.getRs2TileObjectCache().query().interact(rcPortal.getId())) { state = GotrState.LEAVING_ALTAR; return true; } @@ -443,13 +446,13 @@ private static boolean waitForMinigameToStart() { if (state != GotrState.WAITING) { state = GotrState.WAITING; log("Make sure to start the script near the minigame barrier."); - Rs2GameObject.interact(ObjectID.BARRIER_43849, "Peek"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43849, "Peek"); } return state == GotrState.WAITING; } private static boolean enterMinigame() { - if (Rs2GameObject.interact(ObjectID.BARRIER_43700, "quick-pass")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43700, "quick-pass")) { Rs2Player.waitForWalking(); state = GotrState.ENTER_GAME; GotrScript.shouldMineGuardianRemains = true; @@ -476,10 +479,10 @@ private boolean mineHugeGuardianRemain() { } if (!Rs2Inventory.isFull()) { if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); Rs2Player.waitForAnimation(); if (!Rs2Player.isAnimating()) - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); } } else { if (Rs2Inventory.allPouchesFull()) { @@ -490,7 +493,7 @@ private boolean mineHugeGuardianRemain() { Rs2Inventory.fillPouches(); sleep(Rs2Random.randomGaussian(Rs2Random.between(600, 1200), Rs2Random.between(100, 300))); if (!Rs2Inventory.isFull()) { - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); } } } @@ -515,13 +518,13 @@ private void mineGuardianRemains() { if (!isInLargeMine() && !isInHugeMine() && (!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS) || getStartTimer() == -1)) { if (Rs2Walker.walkTo(new WorldPoint(3632, 9503, 0), 20)) { log("Traveling to large mine..."); - Rs2GameObject.interact(ObjectID.RUBBLE_43724); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43724); if (sleepUntil(Rs2Player::isAnimating)) { sleepUntil(GotrScript::isInLargeMine); if (isInLargeMine()) { sleep(Rs2Random.randomGaussian(Rs2Random.between(2000, 2400), Rs2Random.between(100, 300))); log("Interacting with large guardian remains..."); - Rs2GameObject.interact(ObjectID.LARGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LARGE_GUARDIAN_REMAINS); sleepGaussian(1200, 150); } } @@ -535,7 +538,7 @@ private void mineGuardianRemains() { checkPouches(Rs2Random.between(1, 20) == 2, Rs2Random.between(100, 600), Rs2Random.between(100, 300)); repairPouches(); - Rs2GameObject.interact(ObjectID.LARGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LARGE_GUARDIAN_REMAINS); sleepGaussian(1200, 150); } } @@ -549,7 +552,7 @@ private void mineGuardianRemains() { Rs2Combat.setSpecState(true, 1000); } repairPouches(); - Rs2GameObject.interact(ObjectID.GUARDIAN_PARTS_43716); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GUARDIAN_PARTS_43716); sleepGaussian(1200, 150); // we can assume that if the player is mining within the startTimer range, he will get enough guardian remains for the game shouldMineGuardianRemains = false; @@ -558,7 +561,7 @@ private void mineGuardianRemains() { } private void leaveHugeMine() { - Rs2GameObject.interact(38044); + Microbot.getRs2TileObjectCache().query().interact(38044); log("Leave huge mine..."); Global.sleepUntil(() -> !isInHugeMine(), 5000); @@ -582,11 +585,11 @@ private static boolean repairPouches() { private static void repairWithCordelia() { if (!Rs2Inventory.hasDegradedPouch()) return; if (!Rs2Inventory.hasItem(ItemID.ABYSSAL_PEARLS)) return; - Rs2NpcModel pouchRepairNpc = Rs2Npc.getNpc(NpcID.APPRENTICE_CORDELIA_12180); + Rs2NpcModel pouchRepairNpc = Microbot.getRs2NpcCache().query().withId(NpcID.APPRENTICE_CORDELIA_12180).nearest(); if (pouchRepairNpc == null) return; if (!Rs2Npc.hasAction(pouchRepairNpc.getId(), "Repair")) return; - if (!Rs2Npc.canWalkTo(pouchRepairNpc, 10)) return; - if (!Rs2Npc.interact(pouchRepairNpc, "Repair")) return; + if (!Rs2Npc.canWalkTo(pouchRepairNpc.getNpc(), 10)) return; + if (!pouchRepairNpc.click("Repair")) return; Microbot.log("Repairing pouches..."); @@ -762,18 +765,18 @@ public static void resetPlugin() { Microbot.getClient().clearHintArrow(); } - public static TileObject findRcAltar() { - Integer[] altarIds = new Integer[] {ObjectID.ALTAR_34760, ObjectID.ALTAR_34761, ObjectID.ALTAR_34762, ObjectID.ALTAR_34763, ObjectID.ALTAR_34764, + public static Rs2TileObjectModel findRcAltar() { + return Microbot.getRs2TileObjectCache().query().withIds( + ObjectID.ALTAR_34760, ObjectID.ALTAR_34761, ObjectID.ALTAR_34762, ObjectID.ALTAR_34763, ObjectID.ALTAR_34764, ObjectID.ALTAR_34765, ObjectID.ALTAR_34766, ObjectID.ALTAR_34767, ObjectID.ALTAR_34768, ObjectID.ALTAR_34769, ObjectID.ALTAR_34770, - ObjectID.ALTAR_34771, ObjectID.ALTAR_34772, ObjectID.ALTAR_43479}; - return Rs2GameObject.findObject(altarIds); + ObjectID.ALTAR_34771, ObjectID.ALTAR_34772, ObjectID.ALTAR_43479).nearest(); } - public static TileObject findPortalToLeaveAltar() { - Integer[] altarIds = new Integer[] {ObjectID.PORTAL_34748, ObjectID.PORTAL_34749, ObjectID.PORTAL_34750, ObjectID.PORTAL_34751, ObjectID.PORTAL_34752, + public static Rs2TileObjectModel findPortalToLeaveAltar() { + return Microbot.getRs2TileObjectCache().query().withIds( + ObjectID.PORTAL_34748, ObjectID.PORTAL_34749, ObjectID.PORTAL_34750, ObjectID.PORTAL_34751, ObjectID.PORTAL_34752, ObjectID.PORTAL_34753, ObjectID.PORTAL_34754, ObjectID.PORTAL_34755, ObjectID.PORTAL_34756, ObjectID.PORTAL_34757, ObjectID.PORTAL_34758, - ObjectID.PORTAL_34758, ObjectID.PORTAL_34759, ObjectID.PORTAL_43478}; - return Rs2GameObject.findObject(altarIds); + ObjectID.PORTAL_34758, ObjectID.PORTAL_34759, ObjectID.PORTAL_43478).nearest(); } public static boolean leaveMinigame() { GotrScript.isInMiniGame = !isOutsideBarrier() && isInMainRegion(); @@ -781,16 +784,16 @@ public static boolean leaveMinigame() { return true; // Already outside the minigame, successfully left } if(isInLargeMine()) { - Rs2GameObject.interact(ObjectID.RUBBLE_43726); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43726); Rs2Player.waitForAnimation(); sleepUntil(()-> !isInLargeMine()); if (isInLargeMine()){ log("Failed to leave large mine, retrying..."); - return false;// Retry leaving large mine + return false; } - - } - Rs2GameObject.interact(ObjectID.BARRIER_43700, "quick-pass"); + + } + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43700, "quick-pass"); Rs2Player.waitForWalking(); sleepUntil( ()-> {return !(!isOutsideBarrier() && isInMainRegion());}, 200); GotrScript.isInMiniGame = !isOutsideBarrier() && isInMainRegion(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java index d5e59ca53c..ae5d96f0df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java @@ -7,11 +7,9 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.grapefarmer.GrapeFarmerConfig; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.LinkedHashMap; @@ -175,13 +173,13 @@ private void plantSeed(int gameObjectId) { Rs2ItemModel grapeSeed = Rs2Inventory.get(ItemID.GRAPE_SEED); System.out.println("Planting seed..."); if (Rs2Inventory.use(grapeSeed)) { - Rs2GameObject.interact(gameObjectId); + Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact(); Rs2Player.waitForXpDrop(Skill.FARMING); } } private void addSaltpetre(int gameObjectId) { - if (Rs2GameObject.interact(gameObjectId)) { + if (Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { sleep(4000, 4500); } @@ -189,7 +187,7 @@ private void addSaltpetre(int gameObjectId) { private void clearVine(int gameObjectId) { if (!Rs2Player.isMoving() && !Rs2Player.isAnimating(5000) && !Rs2Player.isInteracting()) { - if (Rs2GameObject.interact(gameObjectId)) { + if (Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { Rs2Player.waitForAnimation(2500); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving() && @@ -199,22 +197,22 @@ private void clearVine(int gameObjectId) { } private void pickGrapes(int gameObjectId) { - Rs2NpcModel leprechaun = Rs2Npc.getNpc(0); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withId(0).nearest(); if (leprechaun != null) { if (Rs2Inventory.isFull()) { - Rs2Inventory.useItemOnNpc(ItemID.ZAMORAK_GRAPES, leprechaun); + Rs2Inventory.useItemOnNpc(ItemID.ZAMORAK_GRAPES, leprechaun.getNpc()); sleepUntil(() -> !Rs2Inventory.contains(ItemID.ZAMORAK_GRAPES), 5000); sleep(100,600); if (Rs2Inventory.contains(ItemID.GRAPES)) { Rs2Inventory.use(ItemID.GRAPES); - Rs2Npc.interact(leprechaun); + leprechaun.click(); sleepUntil(() -> !Rs2Inventory.contains(ItemID.GRAPES), 5000); sleep(50, 500); } } } - if (!Rs2Inventory.isFull() && Rs2GameObject.interact(gameObjectId)) { + if (!Rs2Inventory.isFull() && Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { Rs2Player.waitForAnimation(500); } } @@ -247,7 +245,7 @@ private static State getStateForVarbit(int varbitValue) { private static void checkHealth(int gameObjectId) { System.out.println("Interacting with GroundObject ID: " + gameObjectId + " using action: Check-health"); - Rs2GameObject.interact(gameObjectId); + Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact(); Rs2Player.waitForXpDrop(Skill.FARMING); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java index fb0c6e062c..16c99379e0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java @@ -42,7 +42,7 @@ ) public class HerbiboarPlugin extends Plugin { - static final String version = "1.2.4"; + static final String version = "1.2.5"; @Getter @Setter diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java index 359346bd9d..03f1394f84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java @@ -18,8 +18,7 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -584,7 +583,7 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { case TUNNEL: Microbot.status = "Attacking tunnel"; Microbot.log(Level.INFO,"Attacking tunnel"); - if (!attackedTunnel || (Rs2Npc.getNpc("Herbiboar") == null && attackedTunnel)) { + if (!attackedTunnel || (Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread() == null && attackedTunnel)) { int finishId = herbiboarPlugin.getFinishId(); if (finishId > 0) { WorldPoint finishLoc = herbiboarPlugin.getEndLocations().get(finishId - 1); @@ -602,18 +601,18 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { } } } else { - Rs2NpcModel herb = Rs2Npc.getNpc("Herbiboar"); - if (herb != null) setState(HerbiboarState.HARVEST); + Rs2NpcModel herbCheck = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread(); + if (herbCheck != null) setState(HerbiboarState.HARVEST); } break; case HARVEST: Microbot.status = "Harvesting herbiboar"; Microbot.log(Level.INFO,"Harvesting herbiboar"); - Rs2NpcModel herb = Rs2Npc.getNpc("Herbiboar"); + Rs2NpcModel herb = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread(); if (herb != null) { WorldPoint loc = herb.getWorldLocation(); if (Rs2Player.getWorldLocation().distanceTo(loc) <= 8) { - Rs2Npc.interact(herb, "Harvest"); + herb.click("Harvest"); Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting() && !Rs2Player.isMoving(), 5000); incrementHerbisCaught(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java index becea34e9e..d90f0d0d1b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java @@ -26,7 +26,7 @@ @Slf4j public class HerbrunPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private HerbrunConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java index 33a5a1229a..fc5fbbc17d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java @@ -18,8 +18,7 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.timetracking.Tab; @@ -160,12 +159,12 @@ private void getNextPatch() { private boolean handleHerbPatch() { if (Rs2Inventory.isFull()) { - Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool leprechaun"); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearestOnClientThread(); if (leprechaun != null) { Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Grimy", false); if (unNoted != null) { Rs2Inventory.use(unNoted); - Rs2Npc.interact(leprechaun, "Talk-to"); + leprechaun.click("Talk-to"); Rs2Inventory.waitForInventoryChanges(10000); } else { // No grimy herbs to note - try to drop weeds or empty buckets as fallback @@ -179,7 +178,7 @@ private boolean handleHerbPatch() { return false; } - Integer[] ids = { + var obj = Microbot.getRs2TileObjectCache().query().withIds( ObjectID.MYARM_HERBPATCH, ObjectID.FARMING_HERB_PATCH_2, ObjectID.FARMING_HERB_PATCH_4, @@ -190,13 +189,13 @@ private boolean handleHerbPatch() { ObjectID.FARMING_HERB_PATCH_7, ObjectID.MY2ARM_HERBPATCH, ObjectID.FARMING_HERB_PATCH_5 - }; - var obj = Rs2GameObject.findObject(ids); + ).nearest(); if (obj == null) return false; - var state = getHerbPatchState(obj); + var tileObj = Rs2GameObject.findObjectById(obj.getId()); + if (tileObj == null) return false; + var state = getHerbPatchState(tileObj); switch (state) { case "Empty": - // Apply compost if configured if (config.compostType() != CompostType.NONE) { CompostType compost = config.compostType(); if (!Rs2Inventory.hasItem(compost.getItemId())) { @@ -204,35 +203,42 @@ private boolean handleHerbPatch() { return false; } Rs2Inventory.use(compost.getItemId()); - Rs2GameObject.interact(obj, "Compost"); + obj.click("Compost"); Rs2Player.waitForXpDrop(Skill.FARMING, 10000, false); - - // Drop empty bucket if configured (not for bottomless bucket) + if (config.dropEmptyBuckets() && !config.compostType().isBottomless()) { Rs2Inventory.drop(ItemID.BUCKET_EMPTY); } } - // Find the first herb seed in inventory and use its specific ID HerbSeedType seedInInventory = getFirstHerbSeedInInventory(); if (seedInInventory != null) { Rs2Inventory.use(seedInInventory.getItemId()); - Rs2GameObject.interact(obj, "Plant"); - sleepUntil(() -> getHerbPatchState(obj).equals("Growing"), 10000); + obj.click("Plant"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return re != null && getHerbPatchState(re).equals("Growing"); + }, 10000); } else { log("No herb seeds found in inventory for planting"); } return false; case "Harvestable": - Rs2GameObject.interact(obj, "Pick"); - sleepUntil(() -> getHerbPatchState(obj).equals("Empty") || Rs2Inventory.isFull(), 20000); + obj.click("Pick"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return (re != null && getHerbPatchState(re).equals("Empty")) || Rs2Inventory.isFull(); + }, 20000); return false; case "Weeds": - Rs2GameObject.interact(obj, "Rake"); + obj.click("Rake"); Rs2Player.waitForAnimation(10000); return false; case "Dead": - Rs2GameObject.interact(obj, "Clear"); - sleepUntil(() -> getHerbPatchState(obj).equals("Empty"), 10000); + obj.click("Clear"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return re != null && getHerbPatchState(re).equals("Empty"); + }, 10000); return false; default: currentPatch = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java index 76a18fe948..3f93bacfa1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class HouseTabPlugin extends Plugin { - public static final String version = "1.0.8"; + public static final String version = "1.0.9"; @Inject private HouseTabConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java index e2d93f2063..c0d2333352 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java @@ -1,20 +1,19 @@ package net.runelite.client.plugins.microbot.housetab; -import net.runelite.api.GameObject; import net.runelite.api.Point; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.housetab.enums.HOUSETABS_CONFIG; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; + import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Runes; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -67,11 +66,11 @@ public HouseTabScript(HOUSETABS_CONFIG houseTabConfig, String[] playerHouses) { private void lookForHouseAdvertisementObject() { Widget houseAdvertisementPanel = Microbot.getClient().getWidget(HOUSE_ADVERTISEMENT_NAME_PARENT_INTERFACE); - if (!hasSoftClay() || houseAdvertisementPanel != null || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null) + if (!hasSoftClay() || houseAdvertisementPanel != null || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null) return; - boolean success = Rs2GameObject + boolean success = Microbot.getRs2TileObjectCache().query() .interact(HOUSE_ADVERTISEMENT_OBJECT, "View"); @@ -85,7 +84,7 @@ private void lookForPlayerHouse() { if (houseAdvertisementNameWidget == null || houseAdvertisementNameWidget.getChildren() == null) return; if (!hasSoftClay()) return; - if (Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null) + if (Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null) return; int enterHouseButtonHeight = 21; @@ -116,13 +115,17 @@ private void lookForPlayerHouse() { } else { Microbot.getMouse() .click(enterHouseButton.getCanvasLocation()); - sleepUntilOnClientThread(() -> Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null); + sleepUntilOnClientThread(() -> Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null); sleep(2000, 3000); } } private Integer getHouseLectern() { - GameObject lectern = Rs2GameObject.getGameObject(lecternToHouseTabButton.keySet().toArray(new Integer[0])); + Rs2TileObjectModel lectern = null; + for (Integer id : lecternToHouseTabButton.keySet()) { + lectern = Microbot.getRs2TileObjectCache().query().withId(id).nearest(); + if (lectern != null) break; + } if (lectern != null) { lecternTabletWidgetId = lecternToHouseTabButton.get(lectern.getId()); return lectern.getId(); @@ -133,13 +136,13 @@ private Integer getHouseLectern() { public void lookForLectern() { if (getHouseLectern() == null) { Microbot.log("Can't find lectern"); shutdown(); return;} //can't find lectern - if (!hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_ADVERTISEMENT_OBJECT) != null || Microbot.isGainingExp) + if (!hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_ADVERTISEMENT_OBJECT).nearest() != null || Microbot.isGainingExp) return; Widget houseTabInterface = Microbot.getClient().getWidget(lecternTabletWidgetId); - if (houseTabInterface != null || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) return; + if (houseTabInterface != null || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; - boolean success = Rs2GameObject.interact(lecternToHouseTabButton.keySet().stream().mapToInt(Integer::intValue).toArray(), "Study"); + boolean success = Microbot.getRs2TileObjectCache().query().withIds(lecternToHouseTabButton.keySet().stream().mapToInt(Integer::intValue).toArray()).interact("Study"); if (success) { sleepUntilOnClientThread(() -> Microbot.getClient().getWidget(lecternTabletWidgetId) != null); } @@ -148,7 +151,7 @@ public void lookForLectern() { public void createHouseTablet() { Widget houseTabInterface = Microbot.getClient().getWidget(lecternTabletWidgetId); if (houseTabInterface == null) return; - if (!hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) + if (!hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; while (Microbot.getClient().getWidget(lecternTabletWidgetId) != null) { @@ -166,24 +169,26 @@ public void createHouseTablet() { } public void leaveHouse() { - if (hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) + if (hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; - boolean success = Rs2GameObject.interact(HOUSE_PORTAL_OBJECT, "Enter"); + boolean success = Microbot.getRs2TileObjectCache().query().interact(HOUSE_PORTAL_OBJECT, "Enter"); if (success) - sleepUntil(() -> Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null); } public void unnoteClay() { - if (hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_ADVERTISEMENT_OBJECT) == null) + if (hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_ADVERTISEMENT_OBJECT).nearest() == null) return; if (Microbot.getClient().getWidget(14352385) == null) { - do { + while (true) { Microbot.getClientThread().invoke(() -> { Rs2Inventory.use("Soft clay"); }); sleep(300, 380); - } while (!Rs2Npc.interact("Phials", "Use")); + var phials = Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread(); + if (phials != null && phials.click("Use")) break; + } } sleep(2500, 5000); @@ -205,12 +210,12 @@ public boolean run(HouseTabConfig config) { if (Microbot.isGainingExp) return; Rs2Player.toggleRunEnergy(true); - if (Microbot.getClient().getEnergy() < 3000 && !Rs2Widget.hasWidget("Teleport to House") && Rs2GameObject.findObject(new Integer[]{ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION}) != null) { - Rs2GameObject.interact(new int[]{ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION}, "drink"); + if (Microbot.getClient().getEnergy() < 3000 && !Rs2Widget.hasWidget("Teleport to House") && Microbot.getRs2TileObjectCache().query().withIds(ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION).nearest() != null) { + Microbot.getRs2TileObjectCache().query().withIds(ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION).interact("drink"); return; } - boolean isInHouse = Rs2GameObject.getGameObject(lecternToHouseTabButton.keySet().toArray(new Integer[0])) != null; + boolean isInHouse = getHouseLectern() != null; if (isInHouse) { lookForLectern(); createHouseTablet(); @@ -218,12 +223,12 @@ public boolean run(HouseTabConfig config) { } else { unnoteClay(); if (config.ownHouse()) { - if (Rs2GameObject.interact(ObjectID.POH_RIMMINGTON_PORTAL, "Home")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_RIMMINGTON_PORTAL, "Home")) { sleep(800, 1200); } return; } - if (Rs2GameObject.interact(ObjectID.POH_RIMMINGTON_PORTAL, "Friend's house")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_RIMMINGTON_PORTAL, "Friend's house")) { sleepUntil(() -> Rs2Widget.hasWidget("Enter name")); if (Rs2Widget.hasWidget(config.housePlayerName())) { Rs2Widget.clickWidget(config.housePlayerName()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java index 9dfddff0f5..5f3c666818 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class HouseThievingPlugin extends Plugin { - public final static String version = "1.0.2"; + public final static String version = "1.0.3"; @Inject private HouseThievingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java index d48bfcf0d3..aaec16a5dc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java @@ -4,6 +4,7 @@ import net.runelite.api.Quest; import net.runelite.api.QuestState; import net.runelite.api.TileObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; @@ -12,11 +13,9 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; @@ -45,7 +44,7 @@ enum State { } public static State state = State.PICKPOCKETING; - private TileObject currentThievingObject = null; + private Rs2TileObjectModel currentThievingObject = null; private Long lastThievingSearch = null; private ThievingHouse currentThievingHouse = null; private Rs2NpcModel pickpocketNpc = null; @@ -90,7 +89,7 @@ public boolean run(HouseThievingConfig config) { if (currentThievingHouse == null) currentThievingHouse = getThievingHouse(); - var houseNpc = Rs2Npc.getNpc(currentThievingHouse.npcName); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearestOnClientThread(); switch (state) { case PICKPOCKETING: handlePickPocketing(config); @@ -155,7 +154,7 @@ private void handlePickPocketing(HouseThievingConfig config) { } } - var aureliaNpc = Rs2Npc.getNpc("Aurelia"); + var aureliaNpc = Microbot.getRs2NpcCache().query().withName("Aurelia").nearestOnClientThread(); Rs2NpcModel distractedWealthyCitizen = null; if (aureliaNpc != null) { var aureliaAnim = aureliaNpc.getAnimation(); @@ -164,7 +163,7 @@ private void handlePickPocketing(HouseThievingConfig config) { if (distractedWealthyCitizen != null) pickpocketNpc = null; } else if (pickpocketNpc == null) { - var nearbyWealthyCitizens = Rs2Npc.getNpcs(WEALTHY_CITIZEN); + var nearbyWealthyCitizens = Microbot.getRs2NpcCache().query().withName(WEALTHY_CITIZEN).toListOnClientThread().stream(); var aureliaLocation = aureliaNpc.getWorldLocation(); var closestWealthyCitizen = nearbyWealthyCitizens.min(Comparator.comparingInt(a -> a.getWorldLocation().distanceTo(aureliaLocation))); closestWealthyCitizen.ifPresent(rs2NpcModel -> pickpocketNpc = rs2NpcModel); @@ -196,7 +195,7 @@ private void handlePickPocketing(HouseThievingConfig config) { if (Rs2Player.isStunned() && distractedWealthyCitizen == null) sleepUntil(() -> !Rs2Player.isStunned(), 600); - Rs2Npc.interact(targetWealthyCitizen, "Pickpocket"); + targetWealthyCitizen.click("Pickpocket"); Rs2Random.waitEx(600.0, 200.0); sleepUntil(() -> !Rs2Player.isInteracting() && !Rs2Player.isAnimating(), 10000); } @@ -220,11 +219,11 @@ private void doWorldHop() { @Nullable private static Rs2NpcModel getDistractedWealthyCitizen(Rs2NpcModel aureliaNpc) { - return Rs2Npc.getNpcs().filter(npc -> + return Microbot.getRs2NpcCache().query().where(npc -> npc.getWorldLocation().distanceTo(aureliaNpc.getWorldLocation()) <= 5 && npc.getName() != null && npc.getName().equalsIgnoreCase(WEALTHY_CITIZEN) && - npc.isInteracting() - ).findFirst().orElse(null); + npc.isInteractingWithPlayer() + ).toList().stream().findFirst().orElse(null); } private void handleFindingHouse(HouseThievingConfig config) { @@ -254,13 +253,13 @@ private void handleFindingHouse(HouseThievingConfig config) { currentThievingHouse.lockedDoorEgress.getY()); if (lockedDoorTile != null) { var wallObject = lockedDoorTile.getWallObject(); - var houseNpc = Rs2Npc.getNpc(currentThievingHouse.npcName); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearestOnClientThread(); if (wallObject != null && wallObject.getId() == LOCKED_DOOR_ID) { Microbot.log(currentThievingHouse.npcName + " house can be thieved", Level.INFO); attemptWaitForHouseNpc(houseNpc); if (!Rs2Tile.isTileReachable(currentThievingHouse.houseCenter)) { - Rs2GameObject.interact(wallObject); + Microbot.getRs2TileObjectCache().query().withId(wallObject.getId()).interact(); Rs2Random.waitEx(2000.0, 100.0); sleepUntil(() -> !Rs2Player.isInteracting() && !Rs2Player.isAnimating(600)); Rs2Random.waitEx(2000.0, 100.0); @@ -324,30 +323,30 @@ private void handleThievingHouse(Rs2NpcModel houseNpc) { if (currentThievingObject == null) { var tileObject = Rs2Tile.getTile(currentThievingHouse.initialThievingChest.getX(), currentThievingHouse.initialThievingChest.getY()); if (tileObject != null) { - currentThievingObject = Rs2GameObject.getGameObject(currentThievingHouse.initialThievingChest); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(currentThievingHouse.initialThievingChest, 3); if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); } - currentThievingObject = Rs2GameObject.getGameObject(currentThievingHouse.initialThievingChest); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(currentThievingHouse.initialThievingChest, 3); } if (currentThievingObject != null && (lastThievingSearch == null || (elapsedTime != null && elapsedTime > 50000))) { if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); lastThievingSearch = System.currentTimeMillis(); Rs2Random.waitEx(1000.0, 200.0); } } else { - currentThievingObject = Rs2GameObject.getGameObject(hintArrow); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(hintArrow, 3); if (!Rs2Player.isInteracting() || lastThievingSearch == null || (elapsedTime != null && elapsedTime > 50000)) { if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); lastThievingSearch = System.currentTimeMillis(); Rs2Random.waitEx(1000.0, 200.0); } @@ -362,7 +361,7 @@ private boolean exitHouse() { if (!Rs2Camera.isTileOnScreen(windowTileWallObject.getLocalLocation())) { Rs2Camera.turnTo(windowTileWallObject); } - Rs2GameObject.interact(windowTileWallObject, "Exit-window"); + Microbot.getRs2TileObjectCache().query().withId(windowTileWallObject.getId()).interact("Exit-window"); Rs2Random.waitEx(5000.0, 200.0); currentThievingObject = null; lastThievingSearch = null; @@ -400,7 +399,7 @@ private void handleBanking(HouseThievingConfig config) { } if (!Rs2Bank.isOpen() && Rs2Player.distanceTo(BANKING_LOCATION) <= 3) { - var bankingTile = Rs2GameObject.getGameObject(BANKING_TILE_LOCATION); + var bankingTile = Microbot.getRs2TileObjectCache().query().nearest(BANKING_TILE_LOCATION, 3); if (bankingTile != null) Rs2Bank.openBank(bankingTile); else diff --git a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java index 8126c7c609..da77a7b297 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java @@ -10,8 +10,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -120,12 +119,15 @@ private void handleRetrievingState(HunterKebbitsConfig config) { NPC hintNpc = Microbot.getClient().getHintArrowNpc(); if (hintNpc != null && VALID_FALCON_NPC_IDS.contains(hintNpc.getId())) { - Rs2NpcModel model = new Rs2NpcModel(hintNpc); + Rs2NpcModel model = Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() == hintNpc) + .nearest(); boolean retrieved = false; for (int i = 0; i < 5; i++) { if (!isRunning()) break; + if (model == null) break; - if (Rs2Npc.interact(model, "Retrieve")) { + if (model.click("Retrieve")) { retrieved = true; break; } @@ -138,8 +140,9 @@ private void handleRetrievingState(HunterKebbitsConfig config) { currentState = State.CATCHING; } } else { - boolean anyFalconStillActive = Rs2Npc.getNpcs() - .anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean anyFalconStillActive = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; if (!anyFalconStillActive) { currentState = State.CATCHING; } @@ -152,12 +155,15 @@ private void handleRetrievingState(HunterKebbitsConfig config) { private void handleCatchingState(HunterKebbitsConfig config) { String npcName = getKebbit(config).getNpcName(); - if (Rs2Npc.interact(npcName, "Catch")) { + Rs2NpcModel kebbit = Microbot.getRs2NpcCache().query().withName(npcName).nearestOnClientThread(); + if (kebbit != null && kebbit.click("Catch")) { boolean falconActive = false; for (int i = 0; i < 10; i++) { if (!isRunning()) break; - boolean found = Rs2Npc.getNpcs().anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean found = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; if (found || isHintArrowNpcActive()) { falconActive = true; break; @@ -213,7 +219,9 @@ private boolean isHintArrowNpcActive() { * Returns true if falcon is with player (not visible and no active hint arrow). */ private boolean isFalconWithPlayer() { - boolean falconVisible = Rs2Npc.getNpcs().anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean falconVisible = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; return !falconVisible && !isHintArrowNpcActive(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java index 6a9e1a2069..bf4d34589f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java @@ -32,7 +32,7 @@ ) public class HunterKebbitsPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java index 71e92cae92..dd047f6f49 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java @@ -2,14 +2,21 @@ import com.google.inject.Provides; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Projectile; +import net.runelite.api.events.ProjectileMoved; import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.OverlayManager; import javax.inject.Inject; import java.awt.*; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; @PluginDescriptor( name = PluginConstants.MOCROSOFT + "Jad Helper", @@ -25,7 +32,7 @@ ) @Slf4j public class JadPlugin extends Plugin { - static final String version = "1.0.7"; + static final String version = "1.0.9"; @Inject private JadConfig config; @@ -41,6 +48,8 @@ JadConfig provideConfig(ConfigManager configManager) { @Inject JadScript jadScript; + private final Set trackedJadProjectiles = + Collections.newSetFromMap(new IdentityHashMap<>()); @Override @@ -54,5 +63,27 @@ protected void startUp() throws AWTException { protected void shutDown() { jadScript.shutdown(); overlayManager.remove(jadOverlay); + trackedJadProjectiles.clear(); + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + Projectile projectile = event.getProjectile(); + if (projectile == null || !jadScript.isInJadFight()) { + return; + } + + if (projectile.getInteracting() != Microbot.getClient().getLocalPlayer()) { + return; + } + + if (projectile.getRemainingCycles() > 0) { + trackedJadProjectiles.add(projectile); + return; + } + + if (trackedJadProjectiles.remove(projectile)) { + jadScript.onTrackedProjectileImpact(); + } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java index d755b95e96..8126cb8272 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java @@ -2,33 +2,54 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class JadScript extends Script { + private static final int JAD_MAGE_ANIMATION_1 = 7592; + private static final int JAD_MAGE_ANIMATION_2 = 2656; + private static final int JAD_RANGE_ANIMATION_1 = 7593; + private static final int JAD_RANGE_ANIMATION_2 = 2652; + private static final int MAGE_IMPACT_DELAY_TICKS = 4; + private static final int RANGE_IMPACT_DELAY_TICKS = 3; + private static final int PRAYER_SWITCH_LEAD_TICKS = 1; + public static final Map npcAttackCooldowns = new HashMap<>(); + private final Map npcLastAttackAnimations = new HashMap<>(); + private final PriorityQueue attackQueue = + new PriorityQueue<>(Comparator.comparingInt((QueuedJadAttack a) -> a.impactTick).thenComparingLong(a -> a.sequence)); + private long attackSequence = 0; + private JadAttackStyle queuedPrayerStyle = null; public boolean run(JadConfig config) { Microbot.enableAutoRunOn = false; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn() || !super.run()) return; + int currentTick = Microbot.getClient().getTickCount(); - var jadNpcs = Rs2Npc.getNpcs("Jad", false); + List jadNpcs = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .toList(); + Set activeJadIndexes = new HashSet<>(); - for (Rs2NpcModel jadNpc : jadNpcs.collect(Collectors.toList())) { + for (Rs2NpcModel jadNpc : jadNpcs) { if (jadNpc == null) continue; long currentTimeMillis = System.currentTimeMillis(); int npcIndex = jadNpc.getIndex(); + activeJadIndexes.add(npcIndex); if (npcAttackCooldowns.containsKey(npcIndex)) { if (currentTimeMillis - npcAttackCooldowns.get(npcIndex) < 4600) { @@ -38,13 +59,18 @@ public boolean run(JadConfig config) { } } - int npcAnimation = jadNpc.getAnimation(); - handleJadPrayer(npcAnimation); + int npcAnimation = jadNpc.getNpc().getAnimation(); + if (shouldHandleAttackAnimation(npcIndex, npcAnimation)) { + queueJadAttack(npcIndex, npcAnimation, currentTick); + } if (config.shouldAttackHealers()) { handleHealerInteraction(); npcAttackCooldowns.put(npcIndex, currentTimeMillis); } } + + removeStaleNpcData(activeJadIndexes); + processAttackQueue(currentTick); } catch (Exception ex) { System.out.println(ex.getMessage()); } @@ -53,17 +79,21 @@ public boolean run(JadConfig config) { } private void handleHealerInteraction() { - var healer = Rs2Npc.getNpcs("hurkot", false) - .filter(npc -> npc != null && npc.getInteracting() != Microbot.getClient().getLocalPlayer()) - .findFirst() - .orElse(null); + var healer = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("hurkot") && !n.isInteractingWithPlayer()) + .nearest(); if (healer != null) { - Rs2Npc.interact(healer, "attack"); + healer.click("Attack"); } else { var npc = Rs2Player.getInteracting(); - if (npc == null || npc != null && npc.getName().contains("hurkot")) { - Rs2Npc.interact(Rs2Npc.getNpc("Jad", false), "attack"); + if (npc == null || npc.getName().contains("hurkot")) { + var jad = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .nearest(); + if (jad != null) { + jad.click("Attack"); + } } } @@ -73,13 +103,130 @@ private void handleHealerInteraction() { public void shutdown() { super.shutdown(); npcAttackCooldowns.clear(); + npcLastAttackAnimations.clear(); + synchronized (this) { + attackQueue.clear(); + } + queuedPrayerStyle = null; + } + + boolean shouldHandleAttackAnimation(int npcIndex, int animationId) { + if (!isJadAttackAnimation(animationId)) { + npcLastAttackAnimations.remove(npcIndex); + return false; + } + + Integer previousAnimation = npcLastAttackAnimations.put(npcIndex, animationId); + return previousAnimation == null || previousAnimation != animationId; + } + + private boolean isJadAttackAnimation(int animationId) { + return animationId == JAD_MAGE_ANIMATION_1 + || animationId == JAD_MAGE_ANIMATION_2 + || animationId == JAD_RANGE_ANIMATION_1 + || animationId == JAD_RANGE_ANIMATION_2; + } + + private void queueJadAttack(int npcIndex, int animationId, int currentTick) { + JadAttackStyle style = getAttackStyle(animationId); + if (style == null) { + return; + } + + int impactDelayTicks = style == JadAttackStyle.MAGIC ? MAGE_IMPACT_DELAY_TICKS : RANGE_IMPACT_DELAY_TICKS; + int impactTick = currentTick + impactDelayTicks; + synchronized (this) { + attackQueue.offer(new QueuedJadAttack(npcIndex, style, impactTick, attackSequence++)); + } + } + + private JadAttackStyle getAttackStyle(int animationId) { + if (animationId == JAD_MAGE_ANIMATION_1 || animationId == JAD_MAGE_ANIMATION_2) { + return JadAttackStyle.MAGIC; + } + if (animationId == JAD_RANGE_ANIMATION_1 || animationId == JAD_RANGE_ANIMATION_2) { + return JadAttackStyle.RANGED; + } + return null; + } + + private void removeStaleNpcData(Set activeJadIndexes) { + npcLastAttackAnimations.keySet().removeIf(index -> !activeJadIndexes.contains(index)); + synchronized (this) { + attackQueue.removeIf(attack -> !activeJadIndexes.contains(attack.npcIndex)); + } } - private void handleJadPrayer(int animationId) { - if (animationId == 7592 || animationId == 2656) { + private void processAttackQueue(int currentTick) { + synchronized (this) { + QueuedJadAttack nextAttack = attackQueue.peek(); + if (nextAttack == null) { + queuedPrayerStyle = null; + return; + } + + int switchTick = Math.max(0, nextAttack.impactTick - PRAYER_SWITCH_LEAD_TICKS); + if (currentTick < switchTick) { + return; + } + + if (queuedPrayerStyle != nextAttack.style) { + switchPrayer(nextAttack.style); + queuedPrayerStyle = nextAttack.style; + } + } + } + + synchronized void onTrackedProjectileImpact() { + if (attackQueue.isEmpty()) { + queuedPrayerStyle = null; + return; + } + + attackQueue.poll(); + QueuedJadAttack nextAttack = attackQueue.peek(); + if (nextAttack == null) { + queuedPrayerStyle = null; + return; + } + + if (queuedPrayerStyle != nextAttack.style) { + switchPrayer(nextAttack.style); + queuedPrayerStyle = nextAttack.style; + } + } + + boolean isInJadFight() { + var jad = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .nearest(); + return jad != null; + } + + private void switchPrayer(JadAttackStyle style) { + if (style == JadAttackStyle.MAGIC) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); - } else if (animationId == 7593 || animationId == 2652) { + } else { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); } } -} \ No newline at end of file + + private static final class QueuedJadAttack { + private final int npcIndex; + private final JadAttackStyle style; + private final int impactTick; + private final long sequence; + + private QueuedJadAttack(int npcIndex, JadAttackStyle style, int impactTick, long sequence) { + this.npcIndex = npcIndex; + this.style = style; + this.impactTick = impactTick; + this.sequence = sequence; + } + } + + private enum JadAttackStyle { + MAGIC, + RANGED + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java b/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java index 54879771a2..ab99202a8b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.karambwans; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; @@ -11,11 +10,9 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.karambwans.enums.KarambwanBankLocation; @@ -108,9 +105,9 @@ private void walkToRingToBank() { return; } WorldPoint ringLocation = new WorldPoint(2900, 3111, 0); // Karamja ring - GameObject fairyRing = Rs2GameObject.getGameObject(ringLocation); + var fairyRing = Microbot.getRs2TileObjectCache().query().nearest(ringLocation, 3); if (fairyRing != null) { - Rs2GameObject.interact(fairyRing, "Zanaris"); + fairyRing.click("Zanaris"); Rs2Player.waitForAnimation(); } } @@ -153,7 +150,7 @@ private void useBank() { } private void interactWithFishingSpot() { - Rs2Npc.interact(NpcID._0_45_48_KARAMBWAN, "Fish"); + Microbot.getRs2NpcCache().query().withId(NpcID._0_45_48_KARAMBWAN).interact("Fish"); } private void walkToFish() { @@ -165,10 +162,10 @@ private void walkToFish() { } else { Rs2Magic.quickCast(MagicAction.TELEPORT_TO_HOUSE); } - sleepUntil(() -> Rs2GameObject.exists(FAIRY_RING_ID) || Rs2GameObject.exists(SPIRITUAL_FAIRY_TREE_ID), 5000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(FAIRY_RING_ID).nearest() != null || Microbot.getRs2TileObjectCache().query().withId(SPIRITUAL_FAIRY_TREE_ID).nearest() != null, 5000); - boolean interacted = Rs2GameObject.interact(FAIRY_RING_ID, "Last-destination (DKP)") || - Rs2GameObject.interact(SPIRITUAL_FAIRY_TREE_ID, "Last-destination (DKP)"); + boolean interacted = Microbot.getRs2TileObjectCache().query().interact(FAIRY_RING_ID, "Last-destination (DKP)") || + Microbot.getRs2TileObjectCache().query().interact(SPIRITUAL_FAIRY_TREE_ID, "Last-destination (DKP)"); if (interacted) { waitTillPlayerNextToFishingSpot(); @@ -178,15 +175,15 @@ private void walkToFish() { Rs2Player.waitForWalking(); // Ensure the fairy ring at Zanaris is actually loaded before trying to interact. - sleepUntil(() -> Rs2GameObject.getGameObject(zanarisRingPoint) != null, 5000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().nearest(zanarisRingPoint, 3) != null, 5000); - GameObject zanarisRing = Rs2GameObject.getGameObject(zanarisRingPoint); + var zanarisRing = Microbot.getRs2TileObjectCache().query().nearest(zanarisRingPoint, 3); boolean interacted = false; if (zanarisRing != null) { // Prefer the explicit last-destination option, fall back to a generic interact if needed. - interacted = Rs2GameObject.interact(zanarisRing, "Last-destination (DKP)") - || Rs2GameObject.interact(zanarisRing, "Last-destination") - || Rs2GameObject.interact(zanarisRing, "Use"); + interacted = zanarisRing.click("Last-destination (DKP)") + || zanarisRing.click("Last-destination") + || zanarisRing.click("Use"); } if (interacted) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java index e267f3d24c..64c71abff5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java @@ -6,7 +6,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import javax.inject.Inject; @@ -27,7 +27,7 @@ public boolean validate() { @Override public boolean execute() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); Global.sleepUntil(() -> (KittenPlugin.HUNGRY_FIRST_WARNING_TIME_LEFT_IN_SECONDS * 1000) < kittenPlugin.getTimeBeforeHungry()); return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java index 7c27c72b69..47555f7bae 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java @@ -4,7 +4,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import javax.inject.Inject; @@ -26,7 +26,7 @@ public boolean validate() @Override public boolean execute() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Npc.interact(kitten, "Interact")); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> kitten.click("Interact")); if (Rs2Dialogue.sleepUntilHasDialogueOption("Stroke")) { Rs2Dialogue.clickOption("Stroke"); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java index 7c11945068..54a8ff2ec3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java @@ -71,7 +71,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class KittenPlugin extends Plugin { - public static final String version = "1.3.0"; + public static final String version = "1.3.1"; private static final int VAR_PLAYER_FOLLOWER = 447; private static final int WIDGET_ID_DIALOG_NOTIFICATION_GROUP_ID = 229; private static final int WIDGET_ID_DIALOG_PLAYER_TEXT = 6; diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java index ec8d8239a6..5e6cc25b7e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import java.util.concurrent.TimeUnit; @@ -44,12 +43,12 @@ private void handleKittenNeeds(KittenConfig config) { } private void feedKitten() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); sleep(1000, 2000); } private void giveKittenAttention() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten.getNpc())); sleep(1000, 2000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java new file mode 100644 index 0000000000..45113ab7af --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java @@ -0,0 +1,49 @@ +package net.runelite.client.plugins.microbot.kraken; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("kraken") +public interface KrakenConfig extends Config { + + @ConfigItem( + keyName = "listOfItemsToLoot", + name = "Extra loot names", + description = "Comma-separated item names to loot in addition to Kraken uniques (which are always looted).", + position = 0 + ) + default String listOfItemsToLoot() { + return "dragon trident"; + } + + @ConfigItem( + keyName = "toggleLootByValue", + name = "Also loot by GE value", + description = "If on, also loot any item above the min price below. If off, only uniques + extra names are looted.", + position = 1 + ) + default boolean toggleLootByValue() { + return true; + } + + @ConfigItem( + keyName = "minPriceOfItemsToLoot", + name = "Min GE value", + description = "Min GE value (price × qty) to loot. Only used when 'Also loot by GE value' is on.", + position = 2 + ) + default int minPriceOfItemsToLoot() { + return 5000; + } + + @ConfigItem( + keyName = "stopWhenOutOfFood", + name = "Stop when out of food", + description = "If on, stop the plugin when the inventory has no edible food. Only checked between kills (during IDLE) so a kill is never aborted mid-fight.", + position = 3 + ) + default boolean stopWhenOutOfFood() { + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java new file mode 100644 index 0000000000..556560205b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java @@ -0,0 +1,51 @@ +package net.runelite.client.plugins.microbot.kraken; + +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +import javax.inject.Inject; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; + +public class KrakenOverlay extends OverlayPanel { + + private final KrakenScript script; + + @Inject + KrakenOverlay(KrakenPlugin plugin, KrakenScript script) { + super(plugin); + this.script = script; + setPosition(OverlayPosition.TOP_LEFT); + setNaughty(); + } + + @Override + public Dimension render(Graphics2D graphics) { + try { + panelComponent.setPreferredSize(new Dimension(210, 120)); + panelComponent.getChildren().add(TitleComponent.builder() + .text("Kraken " + KrakenPlugin.version) + .color(Color.CYAN) + .build()); + panelComponent.getChildren().add(LineComponent.builder().build()); + panelComponent.getChildren().add(LineComponent.builder() + .left("State:") + .right(script.getState().name()) + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left("Kills:") + .right(String.valueOf(script.getKillCount())) + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(Microbot.status == null ? "" : Microbot.status) + .build()); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + return super.render(graphics); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java new file mode 100644 index 0000000000..fc2c6b6b9a --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.microbot.kraken; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.events.ChatMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.PERT + "Kraken", + description = "AFK Kraken boss: disturbs whirlpools, auto-fights, loots.", + tags = {"kraken", "boss", "slayer", "afk"}, + authors = {"Pert"}, + version = KrakenPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class KrakenPlugin extends Plugin { + public static final String version = "1.0.0"; + + @Inject + private KrakenConfig config; + @Inject + private OverlayManager overlayManager; + @Inject + private KrakenOverlay overlay; + @Inject + private KrakenScript script; + + @Provides + KrakenConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(KrakenConfig.class); + } + + @Override + protected void startUp() { + if (overlayManager != null) { + overlayManager.add(overlay); + } + script.run(config); + } + + @Override + protected void shutDown() { + script.shutdown(); + if (overlayManager != null) { + overlayManager.remove(overlay); + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) { + if (event.getType() != ChatMessageType.GAMEMESSAGE) return; + String msg = event.getMessage().toLowerCase(); + if (msg.contains("stick to your slayer") + || msg.contains("not on a slayer task") + || msg.contains("not currently assigned")) { + script.requestStop("Off-task chat message: \"" + event.getMessage() + "\""); + return; + } + if (msg.contains("you've completed your task") + || msg.contains("you have completed your task") + || msg.contains("completed your slayer task")) { + script.requestStop("Slayer task completed."); + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java new file mode 100644 index 0000000000..52ffcff67c --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java @@ -0,0 +1,262 @@ +package net.runelite.client.plugins.microbot.kraken; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.NPCComposition; +import net.runelite.client.plugins.grounditems.GroundItem; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2LootEngine; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.skills.slayer.Rs2Slayer; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +@Slf4j +public class KrakenScript extends Script { + + private static final int SMALL_WHIRLPOOL_NPC_ID = 5534; + private static final int MAIN_WHIRLPOOL_NPC_ID = 496; + // Attackable Kraken form. Used only to detect death (despawn = kill signal) — we never click it. + private static final int KRAKEN_NPC_ID = 494; + + @Getter + private volatile KrakenState state = KrakenState.IDLE; + @Getter + private volatile int killCount = 0; + + // Tracks NPC scene indexes of small whirlpools we've already clicked this kill, + // so we can fire clicks in sequence without waiting for each to transform. + private final Set clickedSmallIndexes = new HashSet<>(); + + public boolean run(KrakenConfig config) { + state = KrakenState.IDLE; + killCount = 0; + clickedSmallIndexes.clear(); + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!Microbot.isLoggedIn()) return; + if (!super.run()) return; + if (Microbot.pauseAllScripts.get()) return; + + switch (state) { + case IDLE: + handleIdle(config); + break; + case DISTURBING: + handleDisturb(config); + break; + case FIGHTING: + handleFighting(config); + break; + case LOOTING: + handleLooting(config); + break; + case STOPPED: + return; + } + } catch (Exception ex) { + log.error("[Kraken] loop error: {}", ex.getMessage(), ex); + } + }, 0, 600, TimeUnit.MILLISECONDS); + + return true; + } + + private void handleIdle(KrakenConfig config) { + Microbot.status = "Kraken: waiting for whirlpools"; + // Slayer-task gate: Kraken can only be killed while assigned. Re-checked every + // cycle so we exit the moment the task finishes. + if (!isOnKrakenTask()) { + requestStop("Not on a Kraken slayer task."); + return; + } + // Out-of-food gate. Only checked here (between kills) so we never abort mid-fight. + if (config.stopWhenOutOfFood() && Rs2Inventory.getInventoryFood().isEmpty()) { + requestStop("Out of food."); + return; + } + if (anyWhirlpoolSpawned()) { + // Small human reaction delay before the first click fires. + sleep((int) Rs2Random.normalRange(150L, 400L, 0.0)); + state = KrakenState.DISTURBING; + } + } + + public void requestStop(String reason) { + if (state == KrakenState.STOPPED) return; + Microbot.log("[Kraken] Stopping: " + reason); + state = KrakenState.STOPPED; + } + + private static boolean isOnKrakenTask() { + String task = Rs2Slayer.getSlayerTask(); + return task != null && task.toLowerCase().contains("kraken"); + } + + private void handleDisturb(KrakenConfig config) { + // Fire all 5 clicks in sequence: 4 small whirlpools + main. The click alone is + // enough — we don't need to wait for an attack to land. NPC index dedup + // guarantees no double-clicks on the smalls. + java.util.List smalls = Microbot.getRs2NpcCache().query() + .withId(SMALL_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .where(npc -> !clickedSmallIndexes.contains(npc.getIndex())) + .toList(); + + int smallsClicked = clickedSmallIndexes.size(); + for (Rs2NpcModel npc : smalls) { + if (smallsClicked >= 4) break; + Microbot.status = "Kraken: whirlpool " + (smallsClicked + 1) + "/5"; + if (npc.click("Disturb")) { + clickedSmallIndexes.add(npc.getIndex()); + smallsClicked++; + } + sleep(clickDelayMs()); + } + + Microbot.status = "Kraken: whirlpool 5/5"; + Rs2NpcModel main = Microbot.getRs2NpcCache().query() + .withId(MAIN_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .nearest(); + if (main != null) { + main.click("Disturb"); + sleep(clickDelayMs()); + } + + state = KrakenState.FIGHTING; + } + + // Smooth click cadence — mode ~180ms, bounded to [100, 350] with a long right tail. + // Just enough variance to avoid metronomic timing without slowing the sequence down. + private static int clickDelayMs() { + return (int) Rs2Random.skewedRand(180L, 100L, 350L, 0.0); + } + + private void handleFighting(KrakenConfig config) { + Microbot.status = "Kraken: fighting"; + // Confirm the 5-click sequence actually spawned the boss. If a small was + // missed, the main stays as a whirlpool and Kraken never emerges. + boolean spawned = sleepUntil(() -> Microbot.getRs2NpcCache().query() + .withId(KRAKEN_NPC_ID) + .first() != null, 8_000); + if (!spawned) { + Microbot.log("[Kraken] Boss didn't spawn — retrying disturb sequence."); + clickedSmallIndexes.clear(); + state = KrakenState.IDLE; + return; + } + // Death signal: the attackable Kraken NPC despawns the moment it dies. + // (isInCombat() lingers ~8s after the last hit — don't use that here.) + sleepUntil(() -> Microbot.getRs2NpcCache().query() + .withId(KRAKEN_NPC_ID) + .first() == null, 120_000); + // Short human-ish reaction delay before starting to loot. + sleep((int) Rs2Random.normalRange(250L, 600L, 0.0)); + killCount++; + state = KrakenState.LOOTING; + } + + private void handleLooting(KrakenConfig config) { + Microbot.status = "Kraken: looting"; + + // Instanced, so no range / ownership filters needed. No max price, no delay. + LootingParameters params = new LootingParameters( + config.minPriceOfItemsToLoot(), + Integer.MAX_VALUE, + Integer.MAX_VALUE, + /* minItems */ 1, + /* minInvSlots */ 0, + /* delayedLooting */ false, + /* antiLureProtection */ false + ); + + Rs2LootEngine.Builder builder = Rs2LootEngine.with(params) + .withLootAction(Rs2GroundItem::coreLoot); + + // Only: hardcoded uniques + user's name list. Value threshold is opt-in. + // No addCoins/addUntradables — on Leagues, drops are account-bound and report + // as untradeable, which would sweep up every noted stack (monkfish, staves, runes). + builder.addCustom("kraken-uniques", KrakenScript::isKrakenUnique, null); + addCustomNames(builder, config.listOfItemsToLoot()); + if (config.toggleLootByValue()) { + builder.addByValue(); + } + + builder.loot(); + sleep((int) Rs2Random.normalRange(150L, 400L, 0.0)); + + clickedSmallIndexes.clear(); + if (Rs2Inventory.isFull()) { + Microbot.log("[Kraken] Inventory full — stopping."); + state = KrakenState.STOPPED; + } else { + // Back to IDLE — anyWhirlpoolSpawned() polling picks up the respawn quickly. + state = KrakenState.IDLE; + } + } + + private static boolean hasAction(Rs2NpcModel npc, String action) { + NPCComposition comp = npc.getNpc().getTransformedComposition(); + if (comp == null) return false; + for (String a : comp.getActions()) { + if (action.equalsIgnoreCase(a)) return true; + } + return false; + } + + private static boolean anyWhirlpoolSpawned() { + return Microbot.getRs2NpcCache().query() + .withId(SMALL_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .first() != null + || Microbot.getRs2NpcCache().query() + .withId(MAIN_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .first() != null; + } + + private static boolean isKrakenUnique(GroundItem gi) { + String n = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + return n.contains("kraken tentacle") + || n.contains("trident of the seas") + || n.contains("jar of dirt") + || n.contains("pet kraken"); + } + + private static void addCustomNames(Rs2LootEngine.Builder builder, String csvNames) { + if (csvNames == null) return; + Set needles = new HashSet<>(); + Arrays.stream(csvNames.split(",")) + .map(s -> s == null ? "" : s.trim().toLowerCase()) + .filter(s -> !s.isEmpty()) + .forEach(needles::add); + if (needles.isEmpty()) return; + + Predicate byNames = gi -> { + String n = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + for (String needle : needles) { + if (n.contains(needle)) return true; + } + return false; + }; + builder.addCustom("names", byNames, null); + } + + @Override + public void shutdown() { + super.shutdown(); + state = KrakenState.STOPPED; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java new file mode 100644 index 0000000000..0060dc8fa8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.kraken; + +public enum KrakenState { + IDLE, + DISTURBING, + FIGHTING, + LOOTING, + STOPPED +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java new file mode 100644 index 0000000000..4573b12c1f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java @@ -0,0 +1,17 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.coords.WorldPoint; + +@Getter +@RequiredArgsConstructor +public class FireLine { + private final WorldPoint westEnd; + private final WorldPoint eastEnd; + private final int length; + + public int getY() { + return westEnd.getY(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java new file mode 100644 index 0000000000..e221422694 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; + +@ConfigGroup("LeaguesFiremaking") +@ConfigInformation("

AI Firemaking

" + + "

Version: " + LeaguesFiremakingPlugin.version + "

" + + "

Withdraws logs from the bank, finds open space, and lights fires in lines.

" + + "

Scans surrounding tiles to pick the best row, adapts around existing fires and obstacles.

" + + "

Supports Bank Heist briefcase for instant banking.

") +public interface LeaguesFiremakingConfig extends Config { + + @ConfigSection( + name = "General", + description = "General settings", + position = 0 + ) + String generalSection = "general"; + + @ConfigSection( + name = "Banking", + description = "Banking settings", + position = 1 + ) + String bankingSection = "banking"; + + @ConfigItem( + keyName = "logType", + name = "Log type", + description = "Which logs to burn", + position = 0, + section = generalSection + ) + default LogType logType() { + return LogType.LOGS; + } + + @ConfigItem( + keyName = "progressiveMode", + name = "Progressive mode", + description = "Automatically pick the best log for your Firemaking level", + position = 1, + section = generalSection + ) + default boolean progressiveMode() { + return false; + } + + @Range(min = 10, max = 50) + @ConfigItem( + keyName = "scanRadius", + name = "Scan radius", + description = "How many tiles to scan around your starting position for open space", + position = 2, + section = generalSection + ) + default int scanRadius() { + return 25; + } + + @ConfigItem( + keyName = "useBriefcase", + name = "Use Bank Heist briefcase", + description = "Use the banker's briefcase to teleport to a bank instead of walking (Leagues relic)", + position = 0, + section = bankingSection + ) + default boolean useBriefcase() { + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java new file mode 100644 index 0000000000..de87cab3ab --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java @@ -0,0 +1,57 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +import javax.inject.Inject; +import java.awt.*; + +public class LeaguesFiremakingOverlay extends OverlayPanel { + + @Inject + private LeaguesFiremakingScript script; + + @Inject + public LeaguesFiremakingOverlay() { + setPosition(OverlayPosition.TOP_LEFT); + setNaughty(); + } + + @Override + public Dimension render(Graphics2D graphics) { + panelComponent.setPreferredSize(new Dimension(200, 0)); + + panelComponent.getChildren().add(TitleComponent.builder() + .text("AI Firemaking v" + LeaguesFiremakingPlugin.version) + .color(Color.GREEN) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Status") + .right(script.getStatus()) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("State") + .right(script.getState().name()) + .build()); + + FireLine line = script.getCurrentLine(); + if (line != null) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Line length") + .right(String.valueOf(line.getLength())) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Microbot") + .right(Microbot.status) + .build()); + + return super.render(graphics); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java new file mode 100644 index 0000000000..6b8b1a6ce3 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java @@ -0,0 +1,54 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.DV + "AI Firemaking", + description = "Lights fires in lines anywhere — withdraws logs, scans for open tiles, adapts around obstacles", + tags = {"firemaking", "leagues", "microbot", "skilling"}, + version = LeaguesFiremakingPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class LeaguesFiremakingPlugin extends Plugin { + public static final String version = "1.0.0"; + + @Inject + private LeaguesFiremakingConfig config; + + @Inject + private LeaguesFiremakingScript script; + + @Inject + private LeaguesFiremakingOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Provides + LeaguesFiremakingConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(LeaguesFiremakingConfig.class); + } + + @Override + protected void startUp() { + overlayManager.add(overlay); + script.run(config); + } + + @Override + protected void shutDown() { + script.shutdown(); + overlayManager.remove(overlay); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java new file mode 100644 index 0000000000..5d34a7860d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java @@ -0,0 +1,253 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LeaguesFiremakingScript extends Script { + + private static final int TINDERBOX_ID = 590; + private static final String TINDERBOX_NAME = "Tinderbox"; + + @Getter + private State state = State.SCANNING; + @Getter + private String status = "Starting"; + @Getter + private FireLine currentLine; + + private WorldPoint startPosition; + private LogType activeLogType; + + public boolean run(LeaguesFiremakingConfig config) { + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.antibanSetupTemplates.applyFiremakingSetup(); + Rs2AntibanSettings.actionCooldownChance = 0.1; + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (Rs2AntibanSettings.actionCooldownActive) return; + + if (startPosition == null) { + startPosition = Rs2Player.getWorldLocation(); + } + + activeLogType = config.progressiveMode() ? LogType.getBestForLevel() : config.logType(); + + if (activeLogType == null || !activeLogType.hasRequiredLevel()) { + status = "Level too low for " + (activeLogType != null ? activeLogType.getLogName() : "any logs"); + return; + } + + switch (state) { + case SCANNING: + handleScanning(config); + break; + case WALKING_TO_LINE: + handleWalkingToLine(); + break; + case BURNING: + handleBurning(); + break; + case BANKING: + handleBanking(config); + break; + case WALKING_BACK: + handleWalkingBack(config); + break; + } + } catch (Exception ex) { + log.error("LeaguesFiremaking loop error", ex); + Microbot.log(ex.getMessage()); + } + }, 0, 600, TimeUnit.MILLISECONDS); + return true; + } + + private void handleScanning(LeaguesFiremakingConfig config) { + status = "Scanning for open space"; + + if (!Rs2Inventory.hasItem(activeLogType.getItemId())) { + state = State.BANKING; + return; + } + + currentLine = TileScanner.findBestLine(startPosition, config.scanRadius()); + + if (currentLine == null) { + status = "No open space found — try moving to a more open area"; + return; + } + + status = "Found line: " + currentLine.getLength() + " tiles"; + state = State.WALKING_TO_LINE; + } + + private void handleWalkingToLine() { + if (currentLine == null) { + state = State.SCANNING; + return; + } + + WorldPoint eastEnd = currentLine.getEastEnd(); + status = "Walking to east end of line"; + + if (Rs2Player.getWorldLocation().distanceTo(eastEnd) <= 1) { + state = State.BURNING; + return; + } + + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(eastEnd, 0); + } + } + + private void handleBurning() { + if (!Rs2Inventory.hasItem(activeLogType.getItemId())) { + status = "Out of logs"; + state = State.BANKING; + return; + } + + if (Rs2Player.isMoving()) { + status = "Walking after lighting..."; + return; + } + + if (Rs2Player.isAnimating()) { + status = "Lighting animation..."; + return; + } + + if (!Rs2Inventory.hasItem(TINDERBOX_NAME)) { + status = "No tinderbox — banking"; + state = State.BANKING; + return; + } + + // Check if we're standing on a fire — need to step west first + WorldPoint playerPos = Rs2Player.getWorldLocation(); + boolean standingOnFire = TileScanner.hasFire(playerPos); + + if (standingOnFire) { + // Step one tile west to get off the fire + WorldPoint westTile = new WorldPoint(playerPos.getX() - 1, playerPos.getY(), playerPos.getPlane()); + if (!Rs2Tile.isWalkable(westTile)) { + // Can't go west — line is done, rescan + status = "Blocked west — rescanning"; + state = State.SCANNING; + return; + } + Rs2Walker.walkTo(westTile, 0); + sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(westTile) <= 0, 3000); + return; + } + + if (!Rs2Tile.isWalkable(playerPos)) { + status = "Standing on blocked tile — rescanning"; + state = State.SCANNING; + return; + } + + status = "Lighting " + activeLogType.getLogName(); + WorldPoint beforeLight = Rs2Player.getWorldLocation(); + Rs2Inventory.combine(TINDERBOX_NAME, activeLogType.getLogName()); + + // Wait for XP drop (fire lit) then wait for auto-walk west + if (Rs2Player.waitForXpDrop(Skill.FIREMAKING, 10000)) { + sleepUntil(() -> !Rs2Player.getWorldLocation().equals(beforeLight), 3000); + sleep(200, 400); + } + + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + } + + private void handleBanking(LeaguesFiremakingConfig config) { + if (config.useBriefcase()) { + status = "Using briefcase to bank"; + if (!Rs2Inventory.hasItem("Banker's briefcase")) { + status = "No briefcase found — walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return; + } else { + if (!Rs2Bank.isOpen()) { + Rs2Inventory.interact("Banker's briefcase", "Bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + if (!Rs2Bank.isOpen()) return; + } + } + } else { + status = "Walking to bank"; + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.walkToBankAndUseBank()) return; + } + } + + status = "Depositing and withdrawing"; + + Rs2Bank.depositAll(); + sleep(150, 300); + + if (!Rs2Bank.hasItem(TINDERBOX_ID)) { + status = "No tinderbox in bank — stopping"; + Microbot.log("No tinderbox found in bank."); + shutdown(); + return; + } + + Rs2Bank.withdrawOne(TINDERBOX_ID); + sleepUntil(() -> Rs2Inventory.hasItem(TINDERBOX_NAME), 3000); + sleep(150, 300); + + if (!Rs2Bank.hasItem(activeLogType.getItemId())) { + status = "No " + activeLogType.getLogName() + " in bank — stopping"; + Microbot.log("No " + activeLogType.getLogName() + " found in bank."); + shutdown(); + return; + } + + Rs2Bank.withdrawAll(activeLogType.getItemId()); + sleepUntil(() -> Rs2Inventory.hasItem(activeLogType.getItemId()), 3000); + sleep(150, 300); + + Rs2Bank.closeBank(); + state = State.WALKING_BACK; + } + + private void handleWalkingBack(LeaguesFiremakingConfig config) { + status = "Walking back to fire area"; + + if (Rs2Player.getWorldLocation().distanceTo(startPosition) <= config.scanRadius()) { + state = State.SCANNING; + return; + } + + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(startPosition, 3); + } + } + + @Override + public void shutdown() { + super.shutdown(); + Rs2Antiban.resetAntibanSettings(); + startPosition = null; + currentLine = null; + state = State.SCANNING; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java new file mode 100644 index 0000000000..007732bb01 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java @@ -0,0 +1,44 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.api.gameval.ItemID; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum LogType { + LOGS("Logs", ItemID.LOGS, 1), + OAK("Oak logs", ItemID.OAK_LOGS, 15), + WILLOW("Willow logs", ItemID.WILLOW_LOGS, 30), + TEAK("Teak logs", ItemID.TEAK_LOGS, 35), + MAPLE("Maple logs", ItemID.MAPLE_LOGS, 45), + MAHOGANY("Mahogany logs", ItemID.MAHOGANY_LOGS, 50), + YEW("Yew logs", ItemID.YEW_LOGS, 60), + MAGIC("Magic logs", ItemID.MAGIC_LOGS, 75), + REDWOOD("Redwood logs", ItemID.REDWOOD_LOGS, 90); + + private final String logName; + private final int itemId; + private final int levelRequired; + + public boolean hasRequiredLevel() { + return Rs2Player.getSkillRequirement(Skill.FIREMAKING, levelRequired); + } + + public static LogType getBestForLevel() { + LogType best = LOGS; + for (LogType log : values()) { + if (log.hasRequiredLevel()) { + best = log; + } + } + return best; + } + + @Override + public String toString() { + return logName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java new file mode 100644 index 0000000000..98e6caa0e2 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +public enum State { + SCANNING, + WALKING_TO_LINE, + BURNING, + BANKING, + WALKING_BACK +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java new file mode 100644 index 0000000000..d41e0e531d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java @@ -0,0 +1,125 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Slf4j +public class TileScanner { + + private static final int FIRE_ID = 26185; + private static final int FIRE_ID_ALT = 49927; + + public enum TileState { + OPEN, + FIRE, + BLOCKED + } + + public static TileState classifyTile(WorldPoint point, Set fireTiles, Set objectTiles) { + if (fireTiles.contains(point)) return TileState.FIRE; + if (objectTiles.contains(point)) return TileState.BLOCKED; + if (!Rs2Tile.isWalkable(point)) return TileState.BLOCKED; + return TileState.OPEN; + } + + public static List findFireLines(WorldPoint center, int radius) { + Set fireTiles = new HashSet<>(); + Set objectTiles = new HashSet<>(); + + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .forEach(obj -> { + int id = obj.getId(); + WorldPoint loc = obj.getWorldLocation(); + if (id == FIRE_ID || id == FIRE_ID_ALT) { + fireTiles.add(loc); + } else { + objectTiles.add(loc); + } + }); + + List lines = new ArrayList<>(); + int plane = center.getPlane(); + + for (int y = center.getY() - radius; y <= center.getY() + radius; y++) { + int runStartX = -1; + int runLength = 0; + + for (int x = center.getX() - radius; x <= center.getX() + radius; x++) { + WorldPoint point = new WorldPoint(x, y, plane); + TileState state = classifyTile(point, fireTiles, objectTiles); + + if (state == TileState.OPEN) { + if (runStartX == -1) { + runStartX = x; + } + runLength++; + } else { + if (runLength >= 5) { + lines.add(new FireLine( + new WorldPoint(runStartX, y, plane), + new WorldPoint(runStartX + runLength - 1, y, plane), + runLength + )); + } + runStartX = -1; + runLength = 0; + } + } + if (runLength >= 5) { + lines.add(new FireLine( + new WorldPoint(runStartX, y, plane), + new WorldPoint(runStartX + runLength - 1, y, plane), + runLength + )); + } + } + + // Score lines: balance length vs proximity to start position + // A nearby shorter line beats a far-away longer one + lines.sort(Comparator.comparingDouble((FireLine l) -> { + int distance = center.distanceTo(l.getEastEnd()); + // Penalize distance heavily: each tile away reduces effective score + return -(l.getLength() - distance * 0.5); + })); + + return lines; + } + + public static FireLine findBestLine(WorldPoint center, int radius) { + List lines = findFireLines(center, radius); + return lines.isEmpty() ? null : lines.get(0); + } + + public static boolean hasFire(WorldPoint point) { + return Microbot.getRs2TileObjectCache().getStream() + .anyMatch(obj -> obj.getWorldLocation().equals(point) + && (obj.getId() == FIRE_ID || obj.getId() == FIRE_ID_ALT)); + } + + public static Set buildFireSet(WorldPoint center, int radius) { + Set fireTiles = new HashSet<>(); + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .filter(obj -> obj.getId() == FIRE_ID || obj.getId() == FIRE_ID_ALT) + .forEach(obj -> fireTiles.add(obj.getWorldLocation())); + return fireTiles; + } + + public static Set buildObjectSet(WorldPoint center, int radius) { + Set objectTiles = new HashSet<>(); + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .filter(obj -> obj.getId() != FIRE_ID && obj.getId() != FIRE_ID_ALT) + .forEach(obj -> objectTiles.add(obj.getWorldLocation())); + return objectTiles; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java new file mode 100644 index 0000000000..29763dc22d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum AntiAfkMethod { + RANDOM_ARROW_KEY, + BACKSPACE, + CAMERA_ROTATION +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java new file mode 100644 index 0000000000..31929cfa50 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java @@ -0,0 +1,178 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.OverheadTextChanged; +import net.runelite.api.gameval.AnimationID; +import net.runelite.api.gameval.NpcID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import java.util.Set; + +/** + * Event-driven demonic gorilla prayer helper. + * Uses onAnimationChanged for instant prayer switching and + * onHitsplatApplied for hit counting to predict style switches. + */ +@Slf4j +public class DemonicGorillaPrayerHelper { + + private static final Set ALL_GORILLA_IDS = Set.of( + NpcID.MM2_DEMON_GORILLA_1_MELEE, NpcID.MM2_DEMON_GORILLA_1_RANGED, NpcID.MM2_DEMON_GORILLA_1_MAGIC, + NpcID.MM2_DEMON_GORILLA_2_MELEE, NpcID.MM2_DEMON_GORILLA_2_RANGED, NpcID.MM2_DEMON_GORILLA_2_MAGIC + ); + + private static final int ANIM_MAGIC = AnimationID.DEMONIC_GORILLA_MAGIC; // 7225 + private static final int ANIM_MELEE = AnimationID.DEMONIC_GORILLA_PUNCH; // 7226 + private static final int ANIM_RANGED = AnimationID.DEMONIC_GORILLA_RANGE; // 7227 + + @Getter + private String status = "Idle"; + @Getter + private String currentStyle = "Unknown"; + @Getter + private int blockedHitCount = 0; + + private int lastPrayedStyle = -1; + private boolean active = false; + + public void reset() { + status = "Idle"; + currentStyle = "Unknown"; + blockedHitCount = 0; + lastPrayedStyle = -1; + } + + public void setActive(boolean active) { + this.active = active; + if (!active && lastPrayedStyle != -1) { + deactivateCurrentPrayer(); + lastPrayedStyle = -1; + blockedHitCount = 0; + status = "Stopped"; + currentStyle = "None"; + } + } + + public boolean isActive() { + return active; + } + + /** + * Called from @Subscribe onAnimationChanged in the plugin. + * Fires INSTANTLY on the client thread when any actor's animation changes. + */ + public void onAnimationChanged(AnimationChanged event) { + if (!active) return; + + Actor actor = event.getActor(); + if (!(actor instanceof NPC)) return; + + NPC npc = (NPC) actor; + if (!ALL_GORILLA_IDS.contains(npc.getId())) return; + + // Check if this gorilla is targeting us + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null || !player.equals(npc.getInteracting())) return; + + int anim = npc.getAnimation(); + int style = animToStyle(anim); + if (style == -1) return; // Not an attack animation + + if (style != lastPrayedStyle) { + switchPrayer(style); + lastPrayedStyle = style; + blockedHitCount = 0; // Reset count on style switch + } + } + + /** + * Called from @Subscribe onHitsplatApplied in the plugin. + * Tracks blocked hits (0 damage) to predict style switches. + */ + public void onHitsplatApplied(HitsplatApplied event) { + if (!active) return; + + // Only track hitsplats on the player + Actor actor = event.getActor(); + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null || !actor.equals(player)) return; + + // 0 damage = blocked by prayer + if (event.getHitsplat().getAmount() == 0) { + blockedHitCount++; + status = "Praying " + currentStyle + " (" + blockedHitCount + "/3 blocked)"; + if (blockedHitCount >= 3) { + log.info("[DemonicGorilla] 3 blocked hits — expecting style switch!"); + status = "Switch incoming!"; + } + } else { + // Took damage — reset counter (prayer was wrong or boulder hit) + blockedHitCount = 0; + } + } + + /** + * Called from @Subscribe onOverheadTextChanged in the plugin. + * Detects the "Rhaaaaaaa!" scream that confirms a style switch. + */ + public void onOverheadTextChanged(OverheadTextChanged event) { + if (!active) return; + + Actor actor = event.getActor(); + if (!(actor instanceof NPC)) return; + + NPC npc = (NPC) actor; + if (!ALL_GORILLA_IDS.contains(npc.getId())) return; + + String text = event.getOverheadText(); + if (text != null && text.contains("Rhaaaaaaa")) { + log.info("[DemonicGorilla] Style switch confirmed via overhead scream!"); + blockedHitCount = 0; + status = "Style switched!"; + } + } + + private int animToStyle(int animation) { + if (animation == ANIM_MELEE) return 0; + if (animation == ANIM_RANGED) return 1; + if (animation == ANIM_MAGIC) return 2; + return -1; + } + + private void deactivateCurrentPrayer() { + switch (lastPrayedStyle) { + case 0: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, false); break; + case 1: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, false); break; + case 2: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, false); break; + } + log.info("[DemonicGorilla] Deactivated prayer"); + } + + private void switchPrayer(int style) { + switch (style) { + case 0: + currentStyle = "Melee"; + log.info("[DemonicGorilla] → Protect from Melee (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); + break; + case 1: + currentStyle = "Ranged"; + log.info("[DemonicGorilla] → Protect from Missiles (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + break; + case 2: + currentStyle = "Magic"; + log.info("[DemonicGorilla] → Protect from Magic (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + break; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java new file mode 100644 index 0000000000..3a181a4e70 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java @@ -0,0 +1,83 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class EasyClueOpener { + + private static final int SCROLL_BOX_EASY = 24362; + private static final int CLUE_SCROLL_DIG = 29853; + private static final int SPADE = 952; + + @Getter + private String status = "Idle"; + + public void reset() { + status = "Idle"; + } + + public boolean tick(LeaguesToolkitConfig config) { + if (!Rs2Inventory.hasItem(SPADE)) { + status = "No spade in inventory"; + return false; + } + + if (!Rs2Inventory.hasItem(SCROLL_BOX_EASY) && !hasClueScroll()) { + status = "No scroll boxes or clues left"; + return false; + } + + int digMin = Math.min(config.clueDigDelayMin(), config.clueDigDelayMax()); + int digMax = Math.max(config.clueDigDelayMin(), config.clueDigDelayMax()); + int actionDelay = config.clueActionDelay(); + + if (hasClueScroll()) { + if (Rs2Inventory.hasItem(CLUE_SCROLL_DIG)) { + status = "Digging"; + Rs2Inventory.interact(SPADE, "Dig"); + sleep(Rs2Random.between(digMin, digMax)); + sleepUntil(() -> !Rs2Player.isAnimating(), 3000); + sleep(Rs2Random.between(actionDelay / 2, actionDelay)); + return true; + } else { + status = "Dropping non-dig clue"; + dropNonDigClue(); + sleep(Rs2Random.between(actionDelay / 2, actionDelay)); + return true; + } + } + + if (Rs2Inventory.hasItem(SCROLL_BOX_EASY)) { + status = "Opening scroll box"; + Rs2Inventory.interact(SCROLL_BOX_EASY, "Open"); + sleep(Rs2Random.between(actionDelay, actionDelay + 200)); + sleepUntil(this::hasClueScroll, 3000); + return true; + } + + status = "Waiting..."; + return true; + } + + private boolean hasClueScroll() { + return Rs2Inventory.hasItem("Clue scroll (easy)"); + } + + private void dropNonDigClue() { + Rs2Inventory.items(item -> + item.getName() != null + && item.getName().toLowerCase().contains("clue scroll") + && item.getId() != CLUE_SCROLL_DIG + ).findFirst().ifPresent(item -> { + log.info("[EasyClue] Dropping non-dig clue ID={}", item.getId()); + Rs2Inventory.interact(item, "Drop"); + }); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java new file mode 100644 index 0000000000..19d0002d56 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java @@ -0,0 +1,390 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import java.awt.event.KeyEvent; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class GemCutter { + + private static final WorldPoint TOCI_LOCATION = new WorldPoint(1428, 2975, 0); + private static final String TOCI_NPC_NAME = "Toci"; + private static final String CHISEL_NAME = "Chisel"; + private static final int COINS_ID = 995; + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + + @Getter + private GemCutterState state = GemCutterState.WALKING_TO_SHOP; + @Getter + private String status = "Idle"; + + public void reset() { + state = GemCutterState.WALKING_TO_SHOP; + status = "Idle"; + } + + public boolean tick(LeaguesToolkitConfig config) { + GemType gem = config.gemType(); + if (gem == null) { + status = "No gem selected"; + log.info("[GemCutter] tick: no gem selected"); + return false; + } + + log.info("[GemCutter] tick: state={}, cutGems={}, mode={}", state, shouldCut(config), config.gemCutterMode()); + + // Need to bank for coins if idle with low funds + if (state == GemCutterState.WALKING_TO_SHOP + && Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() + && !Rs2Inventory.hasItem(gem.getUncutName()) + && !Rs2Inventory.hasItem(gem.getCutName())) { + state = GemCutterState.BANKING; + } + + switch (state) { + case BANKING: + return handleBanking(config); + case WALKING_TO_SHOP: + return handleWalkingToShop(); + case BUYING: + return handleBuying(gem, config); + case CUTTING: + return handleCutting(gem, config); + case SELLING: + return handleSelling(gem, config); + case BRIEFCASE_BANKING: + return handleBriefcaseBanking(gem, config); + case TELEPORTING_BACK: + return handleTeleportingBack(); + } + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (Rs2Shop.isOpen()) { + Rs2Shop.closeShop(); + return true; + } + + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + if (!Rs2Bank.isOpen()) { + status = "Using equipped briefcase to bank"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleepUntil(Rs2Bank::isOpen, 5000); + return true; + } + } else if (Rs2Inventory.hasItem(BRIEFCASE_NAME)) { + if (!Rs2Bank.isOpen()) { + status = "Using briefcase to bank"; + Rs2Inventory.interact(BRIEFCASE_NAME, "Bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + return true; + } + } else { + if (!Rs2Bank.isOpen()) { + status = "Walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + } + } + + if (!Rs2Bank.isOpen()) return true; + + status = "Withdrawing coins"; + if (!Rs2Bank.hasItem(COINS_ID)) { + status = "No coins in bank — stopping"; + Rs2Bank.closeBank(); + return false; + } + + Rs2Bank.withdrawAll(COINS_ID); + Rs2Bank.closeBank(); + state = GemCutterState.WALKING_TO_SHOP; + return true; + } + + private boolean handleWalkingToShop() { + log.info("[GemCutter] handleWalkingToShop entered"); + + if (Rs2Shop.isOpen()) { + log.info("[GemCutter] Shop already open — transitioning to BUYING"); + status = "Shop already open"; + state = GemCutterState.BUYING; + return true; + } + + WorldPoint playerPos = Rs2Player.getWorldLocation(); + if (playerPos == null) { + log.info("[GemCutter] Player position is null"); + status = "Waiting for player position..."; + return true; + } + + int distance = playerPos.distanceTo(TOCI_LOCATION); + log.info("[GemCutter] Player at {}, Toci at {}, distance={}", playerPos, TOCI_LOCATION, distance); + + // Far away — walk first, don't try to open shop + if (distance > 15) { + status = "Walking to Toci (" + distance + " tiles away)"; + log.info("[GemCutter] Walking to Toci..."); + Rs2Walker.walkTo(TOCI_LOCATION, 4); + sleep(3000, 5000); + return true; + } + + // Close enough — try to open shop + status = "Near Toci — opening shop"; + log.info("[GemCutter] Close enough, opening shop"); + boolean opened = Rs2Shop.openShop(TOCI_NPC_NAME); + log.info("[GemCutter] openShop returned {}", opened); + if (opened) { + sleepUntil(Rs2Shop::isOpen, 5000); + if (Rs2Shop.isOpen()) { + state = GemCutterState.BUYING; + return true; + } + } + return true; + } + + private boolean shouldCut(LeaguesToolkitConfig config) { + GemCutterMode mode = config.gemCutterMode(); + return mode == GemCutterMode.BUY_CUT_SELL || mode == GemCutterMode.BUY_CUT_BANK; + } + + private boolean shouldUseBriefcase(LeaguesToolkitConfig config) { + GemCutterMode mode = config.gemCutterMode(); + return mode == GemCutterMode.BUY_AND_BANK || mode == GemCutterMode.BUY_CUT_BANK; + } + + private GemCutterState nextStateAfterCutting(LeaguesToolkitConfig config) { + return shouldUseBriefcase(config) + ? GemCutterState.BRIEFCASE_BANKING + : GemCutterState.SELLING; + } + + private boolean handleBuying(GemType gem, LeaguesToolkitConfig config) { + if (!Rs2Shop.isOpen()) { + status = "Opening Toci's shop"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not open shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + int uncutCount = Rs2Inventory.count(gem.getUncutName()); + + if (Rs2Inventory.isFull()) { + status = "Inventory full"; + Rs2Shop.closeShop(); + state = shouldCut(config) ? GemCutterState.CUTTING : nextStateAfterCutting(config); + return true; + } + + if (!Rs2Shop.hasStock(gem.getUncutName())) { + if (uncutCount > 0) { + status = "Shop out of stock"; + Rs2Shop.closeShop(); + state = shouldCut(config) ? GemCutterState.CUTTING : nextStateAfterCutting(config); + return true; + } + status = "Shop out of " + gem.getUncutName() + " — waiting"; + sleep(1500, 2500); + return true; + } + + // Mass-click buy at 100-250ms intervals + status = "Rapid-buying " + gem.getUncutName(); + int safetyMax = 32; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + int before = Rs2Inventory.count(gem.getUncutName()); + Rs2Shop.buyItem(gem.getUncutName(), "1"); + sleep(Rs2Random.between(100, 250)); + if (Rs2Inventory.count(gem.getUncutName()) > before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + return true; + } + + private boolean handleCutting(GemType gem, LeaguesToolkitConfig config) { + // Skip cutting if disabled OR no chisel — go straight to sell/bank + if (!shouldCut(config) || !Rs2Inventory.hasItem(CHISEL_NAME)) { + log.info("[GemCutter] Skipping CUTTING (cutGems={}, hasChisel={}), going to next state", + shouldCut(config), Rs2Inventory.hasItem(CHISEL_NAME)); + state = nextStateAfterCutting(config); + return true; + } + + if (!Rs2Inventory.hasItem(gem.getUncutName())) { + status = "All gems cut"; + if (shouldUseBriefcase(config)) { + state = GemCutterState.BRIEFCASE_BANKING; + } else { + state = GemCutterState.SELLING; + } + return true; + } + + status = "Starting to cut " + gem.getCutName(); + Rs2Inventory.use(CHISEL_NAME); + sleep(300, 500); + Rs2Inventory.use(gem.getUncutName()); + + sleep(600, 900); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + + sleep(2000, 3000); + status = "Cutting " + gem.getCutName() + "..."; + sleepUntil(() -> !Microbot.isGainingExp || !Rs2Inventory.hasItem(gem.getUncutName()), 60000); + + return true; + } + + // === SELL MODE === + + private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { + if (!Rs2Inventory.hasItem(gem.getCutName())) { + status = "All cut gems sold — looping"; + if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins()) { + if (Rs2Shop.isOpen()) Rs2Shop.closeShop(); + state = GemCutterState.BANKING; + } else { + // Keep shop open for next buy cycle + state = GemCutterState.BUYING; + } + return true; + } + + if (!Rs2Shop.isOpen()) { + status = "Reopening shop to sell"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not reopen shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + // Mass-click sell from bottom of inventory + int initialCount = Rs2Inventory.count(gem.getCutName()); + status = "Rapid-selling " + gem.getCutName() + " x" + initialCount; + + int safetyMax = initialCount + 5; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + if (!Rs2Inventory.hasItem(gem.getCutName())) break; + + Rs2ItemModel last = Rs2Inventory.items(item -> + gem.getCutName().equalsIgnoreCase(item.getName())) + .reduce((a, b) -> b) + .orElse(null); + if (last == null) break; + int before = Rs2Inventory.count(gem.getCutName()); + + Rs2Inventory.slotInteract(last.getSlot(), "Sell 1"); + sleep(Rs2Random.between(100, 250)); + + if (Rs2Inventory.count(gem.getCutName()) < before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + + return true; + } + + // === BRIEFCASE BANKING MODE === + + private boolean handleBriefcaseBanking(GemType gem, LeaguesToolkitConfig config) { + log.info("[GemCutter] handleBriefcaseBanking: bankOpen={}, wearing={}, hasInInv={}", + Rs2Bank.isOpen(), + Rs2Equipment.isWearing(BRIEFCASE_NAME), + Rs2Inventory.hasItem(BRIEFCASE_NAME)); + + // Step 1: Teleport to bank via briefcase, then open bank + if (!Rs2Bank.isOpen()) { + // First teleport to the bank + status = "Teleporting to bank via briefcase"; + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + log.info("[GemCutter] Clicking equipped briefcase 'Last-destination'"); + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + } else { + status = "No briefcase equipped — walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + sleepUntil(Rs2Bank::isOpen, 10000); + return true; + } + + // Wait for teleport to finish + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 8000); + sleep(500, 1000); + + // Now open the bank normally + status = "Opening bank"; + log.info("[GemCutter] Teleported, now opening bank"); + Rs2Bank.openBank(); + sleepUntil(Rs2Bank::isOpen, 5000); + log.info("[GemCutter] Bank open: {}", Rs2Bank.isOpen()); + return true; + } + + // Step 2: Deposit gems (cut or uncut depending on config) + status = "Depositing gems"; + if (shouldCut(config) && Rs2Inventory.hasItem(gem.getCutName())) { + Rs2Bank.depositAll(gem.getCutName()); + sleep(300, 500); + } + if (!shouldCut(config) && Rs2Inventory.hasItem(gem.getUncutName())) { + Rs2Bank.depositAll(gem.getUncutName()); + sleep(300, 500); + } + + // Step 3: Withdraw coins if low + if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() && Rs2Bank.hasItem(COINS_ID)) { + Rs2Bank.withdrawAll(COINS_ID); + sleep(300, 500); + } + + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + state = GemCutterState.TELEPORTING_BACK; + return true; + } + + private boolean handleTeleportingBack() { + // Walk back to Toci using the web walker + status = "Walking back to Toci"; + log.info("[GemCutter] TELEPORTING_BACK → transitioning to WALKING_TO_SHOP"); + state = GemCutterState.WALKING_TO_SHOP; + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java new file mode 100644 index 0000000000..7334b38e7d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum GemCutterMode { + BUY_AND_BANK("Buy & Bank (fast stockpile uncut gems via briefcase)"), + BUY_CUT_SELL("Buy, Cut & Sell (buy uncut, cut, sell cut back to Toci)"), + BUY_CUT_BANK("Buy, Cut & Bank (buy uncut, cut, bank via briefcase)"); + + private final String description; + + @Override + public String toString() { + return description; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java new file mode 100644 index 0000000000..02c7aabaa1 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java @@ -0,0 +1,11 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum GemCutterState { + BANKING, + WALKING_TO_SHOP, + BUYING, + CUTTING, + SELLING, + BRIEFCASE_BANKING, + TELEPORTING_BACK +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java new file mode 100644 index 0000000000..9e29e52f1e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java @@ -0,0 +1,27 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum GemType { + SAPPHIRE("Uncut sapphire", "Sapphire", 20), + EMERALD("Uncut emerald", "Emerald", 27), + RUBY("Uncut ruby", "Ruby", 34); + + private final String uncutName; + private final String cutName; + private final int craftingLevel; + + public boolean hasRequiredLevel() { + return Rs2Player.getSkillRequirement(Skill.CRAFTING, craftingLevel); + } + + @Override + public String toString() { + return cutName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java new file mode 100644 index 0000000000..5acc04ed78 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java @@ -0,0 +1,454 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.HeadIcon; +import net.runelite.api.NPC; +import net.runelite.api.Perspective; +import net.runelite.api.Projectile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.AnimationID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.SpotanimID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.api.MenuAction; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; + +@Slf4j +public class HesporiBossHelper { + + // NPC IDs + private static final int HESPORI_NORMAL = NpcID.HESPORI; // 8583 + private static final int HESPORI_ECHO = NpcID.LEAGUE_6_HESPORI; // 15615 + private static final int HEALER_ACTIVE = NpcID.HESPORI_HEALER_ACTIVE; // 8584 + private static final int HEALER_INACTIVE = NpcID.HESPORI_HEALER_INACTIVE; // 8585 + + // Attack animations + private static final int ANIM_RANGED = AnimationID.HESPORI_ATTACK_RANGED; // 8224 + private static final int ANIM_SPECIAL = AnimationID.HESPORI_ATTACK_SPECIAL; // 8223 (magic) + + // Leagues 6 Hespori projectile IDs (discovered from in-game testing) + private static final int RANGE_PROJ = 3677; // Ranged attack — pray against, don't dodge + private static final int MAGIC_PROJ = 3678; // Magic attack — pray against, don't dodge + private static final int VINE_PROJ = 3680; // Vine/quadrant explosion — DODGE THIS + + @Getter + private String status = "Idle"; + @Getter + private String currentPrayer = "None"; + @Getter + private String phase = "Combat"; + + private int lastSeenAnimation = -1; + private int lastPrayedStyle = -1; // 0=missiles, 1=magic + @Getter @Setter + private volatile boolean vineDetected = false; + @Getter @Setter + private volatile int dodgeX = -1, dodgeY = -1; + private volatile long lastDodgeTime = 0; + private static final long DODGE_COOLDOWN_MS = 5000; // Don't dodge again for 5 seconds + + private ScheduledExecutorService fastExecutor; + private ScheduledFuture fastLoop; + private LeaguesToolkitConfig config; + + public void start(LeaguesToolkitConfig config) { + this.config = config; + reset(); + if (fastExecutor == null) { + fastExecutor = Executors.newSingleThreadScheduledExecutor(); + } + fastLoop = fastExecutor.scheduleWithFixedDelay(() -> { + try { + if (!Microbot.isLoggedIn()) return; + tick(); + } catch (Exception ex) { + log.error("[Hespori] Fast loop error", ex); + } + }, 0, 150, TimeUnit.MILLISECONDS); + log.info("[Hespori] Fast prayer loop started (150ms)"); + } + + public void stop() { + if (fastLoop != null) { + fastLoop.cancel(false); + fastLoop = null; + } + if (lastPrayedStyle != -1) { + Rs2Prayer.disableAllPrayers(); + lastPrayedStyle = -1; + } + status = "Stopped"; + currentPrayer = "None"; + } + + public void reset() { + status = "Idle"; + currentPrayer = "None"; + phase = "Combat"; + lastSeenAnimation = -1; + lastPrayedStyle = -1; + } + + public void tick() { + Rs2NpcModel hespori = findHespori(); + if (hespori == null) { + status = "No Hespori found"; + return; + } + + // Eat if HP low + if (Rs2Player.getHealthPercentage() <= 50) { + Rs2Player.eatAt(50); + } + + // Drink prayer if low + Rs2Player.drinkPrayerPotionAt(20); + + phase = "Combat"; + + // === FAST LOOP ONLY: prayer switching + eat/drink === + // ALL movement/clicking handled by tickCombat() on the main loop + + // Read boss animation for prayer switching + int anim = Microbot.getClientThread().runOnClientThreadOptional( + () -> hespori.getNpc().getAnimation() + ).orElse(-1); + + if (anim != -1 && anim != lastSeenAnimation) { + lastSeenAnimation = anim; + int style = animToStyle(anim); + if (style != -1 && style != lastPrayedStyle) { + switchPrayer(style); + lastPrayedStyle = style; + } + } + + // Default: keep Protect from Magic on if no recent attack detected + if (lastPrayedStyle == -1) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + currentPrayer = "Magic (default)"; + } + + status = "Fighting Hespori (" + currentPrayer + ")"; + } + + /** + * Flower phase: boss is invulnerable, kill active flowers. + * Boss doesn't use magic during this phase — only ranged. + * Flowers show overhead prayers: + * - Protect from Melee (HeadIcon.MELEE) → use ranged/magic weapon + * - Protect from Ranged+Magic (HeadIcon.RANGE_MAGE) → use melee weapon + */ + private void handleFlowerPhase() { + // Keep Protect from Missiles on during flower phase (boss only uses ranged here) + if (!Rs2Prayer.isPrayerActive(Rs2PrayerEnum.PROTECT_RANGE)) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + currentPrayer = "Missiles (flowers)"; + } + + // Kill ONE flower per tickCombat call — avoids blocking client thread with tight loop + Rs2NpcModel flower = findActiveFlower(); + if (flower == null) return; + + // Check overhead prayer for weapon switch + HeadIcon flowerPrayer = Microbot.getClientThread().runOnClientThreadOptional( + () -> flower.getHeadIcon() + ).orElse(null); + + if (flowerPrayer != null && config != null) { + boolean needsMelee = (flowerPrayer == HeadIcon.RANGED || flowerPrayer == HeadIcon.MAGIC + || flowerPrayer == HeadIcon.RANGE_MAGE); + if (needsMelee) { + String meleeWeapon = config.hesporiMeleeWeapon(); + if (!Rs2Equipment.isWearing(meleeWeapon) && Rs2Inventory.hasItem(meleeWeapon)) { + Rs2Inventory.wield(meleeWeapon); + sleep(150, 300); + } + } else { + String mageWeapon = config.hesporiMageWeapon(); + if (!Rs2Equipment.isWearing(mageWeapon) && Rs2Inventory.hasItem(mageWeapon)) { + Rs2Inventory.wield(mageWeapon); + sleep(150, 300); + } + } + } + + status = "Killing flower"; + Microbot.getRs2NpcCache().query() + .withId(flower.getId()) + .interact(flower.getId(), "Attack"); + } + + // Vine dodge is handled by handleVineDodge() called from tickCombat + + /** + * Called from tickCombat (main 1-second loop) — safe to click/walk here. + */ + public boolean handleVineDodge() { + if (!vineDetected) return false; + vineDetected = false; + dodgeX = -1; + dodgeY = -1; + + // Cooldown — don't dodge again within 5 seconds + if (System.currentTimeMillis() - lastDodgeTime < DODGE_COOLDOWN_MS) { + return false; + } + + WorldPoint myPos = Rs2Player.getWorldLocation(); + if (myPos == null) return false; + + // Determine which quadrant we're in relative to boss center + // Boss LocalPoint is always (7104, 7104) — convert to world offset direction + // Just move 5 tiles in the opposite direction from boss center + LocalPoint myLocal = Microbot.getClientThread().runOnClientThreadOptional(() -> { + var player = Microbot.getClient().getLocalPlayer(); + return player != null ? player.getLocalLocation() : null; + }).orElse(null); + + if (myLocal == null) return false; + + int bossLocalX = 7104, bossLocalY = 7104; + int dx = (myLocal.getX() > bossLocalX) ? -5 : 5; + int dy = (myLocal.getY() > bossLocalY) ? -5 : 5; + + WorldPoint dodgeTarget = new WorldPoint(myPos.getX() + dx, myPos.getY() + dy, myPos.getPlane()); + + status = "Dodging vine!"; + log.info("[Hespori] Vine dodge — Rs2Walker.walkFastCanvas to {}", dodgeTarget); + Rs2Walker.walkFastCanvas(dodgeTarget); + lastDodgeTime = System.currentTimeMillis(); + sleep(600, 900); + return true; + } + + private Rs2NpcModel findHespori() { + // Both normal and Echo Hespori use the same NPC ID (8583) + return findAliveNpc(HESPORI_NORMAL); + } + + /** + * Find an active flower by ID first, falling back to name-based search for Echo variants. + */ + private Rs2NpcModel findActiveFlower() { + // Try by known ID first + Rs2NpcModel byId = findAliveNpc(HEALER_ACTIVE); + if (byId != null) return byId; + + // Fallback: search by name "Flower" for Echo variants that might use different IDs + return Microbot.getRs2NpcCache().query() + .where(npc -> { + String name = npc.getName(); + return name != null && name.equalsIgnoreCase("Flower"); + }) + .where(npc -> !npc.isDead()) + .where(npc -> { + // Only active flowers (not inactive/cyan ones) + HeadIcon icon = Microbot.getClientThread().runOnClientThreadOptional( + () -> npc.getHeadIcon() + ).orElse(null); + return icon != null; // Active flowers have overhead prayers, inactive don't + }) + .nearest(); + } + + private Rs2NpcModel findAliveNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .where(npc -> !npc.isDead()) + .nearest(); + } + + /** + * Called from the main 1-second toolkit loop to handle attacking. + * Separated from the fast prayer loop to avoid threading issues with doInvoke. + */ + public void tickCombat() { + log.info("[Hespori] tickCombat called"); + Rs2NpcModel hespori = findHespori(); + if (hespori == null) { + log.info("[Hespori] tickCombat: findHespori returned null — NPC not found or dead"); + return; + } + log.info("[Hespori] tickCombat: found NPC id={}, dead={}", hespori.getId(), hespori.isDead()); + + // Handle vine dodge FIRST — highest priority + if (handleVineDodge()) { + return; + } + + // Handle flower phase attacking + Rs2NpcModel activeFlower = findActiveFlower(); + if (activeFlower != null) { + handleFlowerPhase(); + return; + } + + // Ensure main weapon equipped + if (config != null) { + String mainWeapon = config.hesporiMainWeapon(); + if (!Rs2Equipment.isWearing(mainWeapon) && Rs2Inventory.hasItem(mainWeapon)) { + Rs2Inventory.wield(mainWeapon); + sleep(150, 300); + } + } + + // NOTE: No walk range check — player is already in the circular arena. + // Instance coordinate mismatch makes distance calculations unreliable. + + // Only click attack if not already interacting with Hespori + boolean alreadyFighting = Microbot.getClientThread().runOnClientThreadOptional(() -> { + var player = Microbot.getClient().getLocalPlayer(); + if (player == null) return false; + var target = player.getInteracting(); + if (target == null) return false; + return target.equals(hespori.getNpc()); + }).orElse(false); + + if (!alreadyFighting) { + status = "Attacking Hespori"; + // Dump full NPC state for debugging, then attack + Microbot.getClientThread().invoke(() -> { + try { + NPC rawNpc = hespori.getNpc(); + if (rawNpc == null) { + log.error("[Hespori] rawNpc is null"); + return; + } + + // Dump all info + log.info("[Hespori] === NPC DEBUG ==="); + log.info("[Hespori] NPC id={}, index={}, name={}", rawNpc.getId(), rawNpc.getIndex(), rawNpc.getName()); + log.info("[Hespori] worldLocation={}", rawNpc.getWorldLocation()); + log.info("[Hespori] localLocation={}", rawNpc.getLocalLocation()); + log.info("[Hespori] animation={}, isDead={}", rawNpc.getAnimation(), rawNpc.isDead()); + + var comp = Microbot.getClient().getNpcDefinition(rawNpc.getId()); + if (comp != null) { + log.info("[Hespori] Composition actions: {}", java.util.Arrays.toString(comp.getActions())); + } else { + log.error("[Hespori] NPCComposition is null for id={}", rawNpc.getId()); + } + + var canvasPoly = rawNpc.getCanvasTilePoly(); + log.info("[Hespori] canvasTilePoly={}", canvasPoly != null ? canvasPoly.getBounds() : "null"); + + // Verify the action exists and find its index + String[] actions = comp != null ? comp.getActions() : null; + if (actions == null) { + log.error("[Hespori] No actions on NPC"); + return; + } + + // Find attack action — don't hardcode, check what's available + String attackAction = null; + int attackIndex = -1; + for (int i = 0; i < actions.length; i++) { + if (actions[i] != null && actions[i].toLowerCase().contains("attack")) { + attackAction = actions[i]; + attackIndex = i; + break; + } + } + + if (attackAction == null) { + log.error("[Hespori] No attack-like action found in: {}", java.util.Arrays.toString(actions)); + return; + } + + log.info("[Hespori] Found action '{}' at index {}", attackAction, attackIndex); + + MenuAction menuAction = MenuAction.of(MenuAction.NPC_FIRST_OPTION.getId() + attackIndex); + log.info("[Hespori] MenuAction opcode: {} (id={})", menuAction, menuAction.getId()); + + Microbot.getClient().menuAction( + 0, 0, menuAction, rawNpc.getIndex(), -1, + attackAction, rawNpc.getName() != null ? rawNpc.getName() : "" + ); + log.info("[Hespori] menuAction dispatched successfully"); + } catch (Exception e) { + log.error("[Hespori] Attack error", e); + } + }); + sleep(1200, 1500); + } else { + status = "Fighting Hespori (" + currentPrayer + ")"; + } + } + + private boolean isMeleeWeaponEquipped(LeaguesToolkitConfig config) { + if (config == null) return false; + String mainWeapon = config.hesporiMainWeapon(); + String meleeWeapon = config.hesporiMeleeWeapon(); + // If the main weapon is the same as the melee weapon, we're in melee mode + return Rs2Equipment.isWearing(mainWeapon) && mainWeapon.equalsIgnoreCase(meleeWeapon); + } + + private int animToStyle(int animation) { + if (animation == ANIM_RANGED) return 0; // Ranged → Protect from Missiles + if (animation == ANIM_SPECIAL) return 1; // Magic → Protect from Magic + return -1; + } + + private void switchPrayer(int style) { + switch (style) { + case 0: + currentPrayer = "Missiles"; + log.info("[Hespori] → Protect from Missiles (ranged)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + break; + case 1: + currentPrayer = "Magic"; + log.info("[Hespori] → Protect from Magic"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + break; + } + } + + private WorldPoint findSafeTile(WorldPoint center, int distance) { + Map dangerousTiles = Rs2Tile.getDangerousGraphicsObjectTiles(); + List candidates = new ArrayList<>(); + + for (int dx = -distance; dx <= distance; dx++) { + for (int dy = -distance; dy <= distance; dy++) { + if (dx == 0 && dy == 0) continue; + WorldPoint candidate = new WorldPoint( + center.getX() + dx, center.getY() + dy, center.getPlane()); + if (!dangerousTiles.containsKey(candidate) && Rs2Tile.isWalkable(candidate)) { + candidates.add(candidate); + } + } + } + + if (candidates.isEmpty()) return null; + + Rs2NpcModel boss = findHespori(); + if (boss != null) { + WorldPoint bossLoc = boss.getWorldLocation(); + candidates.sort(Comparator.comparingInt(c -> c.distanceTo(bossLoc))); + } + + return candidates.get(0); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java new file mode 100644 index 0000000000..6dfd645e14 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java @@ -0,0 +1,188 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.gameval.NpcID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; + +@Slf4j +public class KrakenBossHelper { + + // NPC IDs + private static final int BOSS_WHIRLPOOL = NpcID.SLAYER_KRAKEN_BOSS_WHIRLPOOL; // 496 + private static final int BOSS_AWAKE = NpcID.SLAYER_KRAKEN_BOSS; // 494 + private static final int TENTACLE_WHIRLPOOL = NpcID.SLAYER_KRAKEN_BOSS_TENTACLE_WHIRLPOOL; // 5534 + private static final int TENTACLE_AWAKE = NpcID.SLAYER_KRAKEN_BOSS_TENTACLE; // 5535 + + @Getter + private String status = "Idle"; + private boolean antibanInitialized = false; + + public void reset() { + status = "Idle"; + antibanInitialized = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Initialize antiban on first tick + if (!antibanInitialized) { + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.antibanSetupTemplates.applyCombatSetup(); + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.takeMicroBreaks = true; + Rs2AntibanSettings.microBreakChance = 0.02; + Rs2AntibanSettings.actionCooldownChance = 0.15; + Rs2AntibanSettings.moveMouseOffScreen = true; + antibanInitialized = true; + log.info("[Kraken] Antiban initialized"); + } + + // Keep Protect from Magic on at all times + if (!Rs2Prayer.isPrayerActive(Rs2PrayerEnum.PROTECT_MAGIC)) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + } + + // Eat food if HP low + if (Rs2Player.getHealthPercentage() <= config.krakenEatAt()) { + status = "Eating"; + Rs2Player.eatAt(config.krakenEatAt()); + sleep(300, 500); + } + + // Drink prayer pot if low + if (config.krakenDrinkPrayer()) { + Rs2Player.drinkPrayerPotionAt(config.krakenPrayerThreshold()); + } + + // Wait if already in combat + if (Rs2Player.isAnimating() || Rs2Player.isInteracting()) { + // Check what we're fighting + Rs2NpcModel awakeTentacle = findAliveNpc(TENTACLE_AWAKE); + Rs2NpcModel boss = findAliveNpc(BOSS_AWAKE); + if (awakeTentacle != null) { + status = "Killing tentacle"; + return true; + } + if (boss != null) { + status = "Fighting Kraken"; + return true; + } + } + + // Step 1: Kill any awake tentacles first + Rs2NpcModel awakeTentacle = findAliveNpc(TENTACLE_AWAKE); + if (awakeTentacle != null) { + if (!Rs2Combat.inCombat()) { + status = "Attacking tentacle"; + awakeTentacle.click("Attack"); + sleep(600, 900); + } else { + status = "Killing tentacle"; + } + return true; + } + + // Step 2: Disturb next tentacle whirlpool + Rs2NpcModel tentaclePool = findNpc(TENTACLE_WHIRLPOOL); + if (tentaclePool != null) { + status = "Disturbing tentacle"; + tentaclePool.click("Disturb"); + sleep(600, 900); + return true; + } + + // Step 3: All tentacles dead — disturb the boss whirlpool + Rs2NpcModel bossPool = findNpc(BOSS_WHIRLPOOL); + if (bossPool != null) { + status = "Disturbing Kraken boss"; + bossPool.click("Disturb"); + sleep(600, 900); + return true; + } + + // Step 4: Boss is awake — attack it + Rs2NpcModel boss = findAliveNpc(BOSS_AWAKE); + if (boss != null) { + if (!Rs2Combat.inCombat()) { + status = "Attacking Kraken"; + boss.click("Attack"); + sleep(600, 900); + } else { + status = "Fighting Kraken"; + } + return true; + } + + // Step 4: Boss is dead — loot valuable drops before respawn + if (lootValuableDrops()) { + status = "Looting"; + return true; + } + + // Between kills — safe to do antiban here while waiting + if (findNpc(BOSS_WHIRLPOOL) == null && findNpc(BOSS_AWAKE) == null) { + status = "Waiting for respawn..."; + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + return true; + } + + // Also safe to antiban before starting a new kill cycle + if (findNpc(BOSS_WHIRLPOOL) != null && findNpc(TENTACLE_WHIRLPOOL) != null + && findAliveNpc(TENTACLE_AWAKE) == null && findAliveNpc(BOSS_AWAKE) == null) { + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + } + + status = "Idle"; + return true; + } + + /** + * Loot high-value drops like Trident of the seas (full). + * Eats food to make space if inventory is full. + */ + private boolean lootValuableDrops() { + // Check for valuable drops nearby + String[] valuableItems = {"Trident of the seas (full)", "Kraken tentacle", "Jar of dirt", "Pet kraken"}; + for (String item : valuableItems) { + if (Rs2GroundItem.exists(item, 10)) { + if (Rs2Inventory.isFull()) { + // Eat food to make space + if (!Rs2Inventory.getInventoryFood().isEmpty()) { + Rs2Player.eatAt(100); + sleep(300, 500); + } + } + Rs2GroundItem.loot(item, 10); + sleep(600, 900); + return true; + } + } + return false; + } + + private Rs2NpcModel findNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .nearest(); + } + + private Rs2NpcModel findAliveNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .where(npc -> !npc.isDead()) + .nearest(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java new file mode 100644 index 0000000000..b234e61e7f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -0,0 +1,513 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; + +@ConfigGroup("LeaguesToolkit") +@ConfigInformation("

Leagues Toolkit (BETA)

" + + "

Version: " + LeaguesToolkitPlugin.version + "

" + + "

This plugin is in BETA. Not all features are fully polished — some boss helpers " + + "may have edge cases in instanced areas. Use Prayer/eat only mode for boss helpers " + + "if the full automation isn't working reliably.

" + + "
" + + "

Anti-AFK: Presses a random arrow key before the idle timer logs you out. " + + "Great for long AFK sessions with auto-bank relics (e.g. Endless Harvest).

" + + "

Demonic Gorilla Prayer: Event-driven prayer switching — reacts instantly " + + "to gorilla attack animations via onAnimationChanged (no polling delay). " + + "Tracks blocked hits (0 damage) to predict style switches (3 blocked = switch incoming). " + + "Detects the 'Rhaaaaaaa!' overhead scream to confirm switches.

" + + "

Toci's Gem Store: Automated gem trading at Toci in Aldarin. Three modes:

" + + "
    " + + "
  • Buy & Bank — fast stockpile uncut gems via Banker's Briefcase Last-destination.
  • " + + "
  • Buy, Cut & Sell — buy uncut, cut with chisel, sell cut back to Toci.
  • " + + "
  • Buy, Cut & Bank — buy uncut, cut, bank via briefcase.
  • " + + "
" + + "

Wealthy Citizen Thieving: Pickpockets Wealthy citizens (Larcenist relic required " + + "for 100% success). Opens coin pouches at configurable threshold (max 280).

" + + "

Easy Clue Opener: Aldarin bank easy clue farming. Opens scroll boxes, " + + "digs if clue ID is 29853, drops non-dig clues, stacks caskets. Configurable delays.

" + + "

Hespori (Echo): Prayer switches between Magic and Missiles based on attack animation. " + + "Kills flowers with correct combat style based on their overhead prayer (weapon switching). " + + "Vine dodge moves to opposite quadrant when projectile 3680 is detected. " + + "Prayer/eat only mode available for manual combat with automated prayer.

" + + "

Kraken Boss: Full automation — disturbs tentacles one by one (Disturb → Attack → kill), " + + "then wakes and kills the boss. Loots Trident of the seas, Kraken tentacle, Jar of dirt, Pet kraken. " + + "Keeps Protect from Magic on. Start inside the boss room with a slayer task.

" + + "

Snape Grass Telegrab: Walks to spawn, casts Telekinetic Grab on repeat. " + + "Banks via briefcase Last-destination or walks to nearest bank when full. " + + "Requires 33 Magic, law runes, air runes or air staff.

" + + "

Ourania Altar: Crafts runes at ZMI, banks via briefcase to Eniola. " + + "Deposits only crafted runes (keeps air runes + noted pure essence). " + + "Works with Transmutation running concurrently. Eniola requires auto-pay runes.

" + + "

Transmutation: Casts Alchemic Divergence/Convergence on noted items " + + "to upgrade/downgrade through tiers. Dropdown selection for start and target items. " + + "Shop-aware timeout (pauses detection when shop is open). " + + "Have starting items noted in inventory before enabling.

") +public interface LeaguesToolkitConfig extends Config { + + @ConfigSection( + name = "Anti-AFK", + description = "Prevents the idle-timeout logout during long AFK sessions", + position = 0 + ) + String antiAfkSection = "antiAfkSection"; + + @ConfigItem( + keyName = "enableAntiAfk", + name = "Enable anti-AFK", + description = "Periodically triggers input to reset the idle timer so you never get logged out", + position = 0, + section = antiAfkSection + ) + default boolean enableAntiAfk() { + return true; + } + + @ConfigItem( + keyName = "antiAfkMethod", + name = "Input method", + description = "What kind of input to send. Random arrow keys look most natural.", + position = 1, + section = antiAfkSection + ) + default AntiAfkMethod antiAfkMethod() { + return AntiAfkMethod.RANDOM_ARROW_KEY; + } + + @Range(min = 50, max = 5000) + @ConfigItem( + keyName = "antiAfkBufferMin", + name = "Trigger buffer min (ticks)", + description = "Minimum ticks before the client's AFK threshold at which to fire input", + position = 2, + section = antiAfkSection + ) + default int antiAfkBufferMin() { + return 500; + } + + @Range(min = 50, max = 5000) + @ConfigItem( + keyName = "antiAfkBufferMax", + name = "Trigger buffer max (ticks)", + description = "Maximum ticks before the client's AFK threshold at which to fire input", + position = 3, + section = antiAfkSection + ) + default int antiAfkBufferMax() { + return 1500; + } + + @ConfigSection( + name = "Demonic Gorilla Prayer", + description = "Auto-switches protection prayers based on the gorilla's current attack style", + position = 1, + closedByDefault = true + ) + String gorillaPrayerSection = "gorillaPrayerSection"; + + @ConfigItem( + keyName = "enableGorillaPrayer", + name = "Enable", + description = "Automatically switches between Protect from Melee, Missiles, and Magic " + + "based on which demonic gorilla variant is attacking you. " + + "Detects style changes by NPC ID transformation.", + position = 0, + section = gorillaPrayerSection + ) + default boolean enableGorillaPrayer() { + return false; + } + + @ConfigSection( + name = "Toci's Gem Store", + description = "Automated gem buying, cutting, and selling/banking at Toci in Aldarin", + position = 2, + closedByDefault = true + ) + String gemCutterSection = "gemCutterSection"; + + @ConfigItem( + keyName = "enableGemCutter", + name = "Enable", + description = "Walks to Toci's Gem Store in Aldarin and runs the selected mode. Requires coins in inventory.", + position = 0, + section = gemCutterSection + ) + default boolean enableGemCutter() { + return false; + } + + @ConfigItem( + keyName = "gemCutterMode", + name = "Mode", + description = "Buy & Bank: fast stockpile uncut gems (briefcase required). " + + "Buy, Cut & Sell: buy uncut, cut with chisel, sell cut back to Toci. " + + "Buy, Cut & Bank: buy uncut, cut, bank via briefcase.", + position = 1, + section = gemCutterSection + ) + default GemCutterMode gemCutterMode() { + return GemCutterMode.BUY_AND_BANK; + } + + @ConfigItem( + keyName = "gemType", + name = "Gem", + description = "Which gem to buy/cut. Cut modes require a chisel and the crafting level.", + position = 2, + section = gemCutterSection + ) + default GemType gemType() { + return GemType.RUBY; + } + + @Range(min = 1000, max = 1_000_000) + @ConfigItem( + keyName = "gemCutterMinCoins", + name = "Min coins to keep", + description = "When coins drop below this, withdraw more from the bank", + position = 3, + section = gemCutterSection + ) + default int gemCutterMinCoins() { + return 10_000; + } + + @ConfigSection( + name = "Wealthy Citizen Thieving", + description = "Pickpockets Wealthy citizens, opens coin pouches at a threshold", + position = 3, + closedByDefault = true + ) + String thievingSection = "thievingSection"; + + @ConfigItem( + keyName = "enableThieving", + name = "Enable", + description = "Pickpockets the nearest Wealthy citizen with 100% success (Larcenist relic required). " + + "Opens coin pouches at the configured threshold. Stand near a Wealthy citizen before enabling.", + position = 0, + section = thievingSection + ) + default boolean enableThieving() { + return false; + } + + @Range(min = 1, max = 280) + @ConfigItem( + keyName = "coinPouchThreshold", + name = "Open pouches at", + description = "Open coin pouches when this many have accumulated (max 280 before they auto-destroy)", + position = 1, + section = thievingSection + ) + default int coinPouchThreshold() { + return 200; + } + + @ConfigSection( + name = "Snape Grass Telegrab", + description = "Telegrab snape grass at a fixed location", + position = 4, + closedByDefault = true + ) + String snapeGrassSection = "snapeGrassSection"; + + @ConfigItem( + keyName = "enableSnapeGrass", + name = "Enable", + description = "Walks to snape grass spawn (1736, 3170), casts Telekinetic Grab, waits for respawn, repeats. " + + "Requires 33 Magic, law runes, and air runes or air staff equipped.", + position = 0, + section = snapeGrassSection + ) + default boolean enableSnapeGrass() { + return false; + } + + @ConfigSection( + name = "Easy Clue Opener", + description = "Opens scroll boxes, digs dig-clues, drops non-dig clues, opens reward caskets", + position = 5, + closedByDefault = true + ) + String easyClueSection = "easyClueSection"; + + @ConfigItem( + keyName = "enableEasyClue", + name = "Enable", + description = "Uses the Aldarin bank easy clue method to farm reward caskets. " + + "Opens Scroll box (easy), checks if the clue is a dig type (ID 29853) — " + + "if so, digs with spade repeatedly until you get a casket or a different clue. " + + "Non-dig clues are dropped and the next scroll box is opened. " + + "Caskets stack in your inventory. Requires a spade and scroll boxes.", + position = 0, + section = easyClueSection + ) + default boolean enableEasyClue() { + return false; + } + + @Range(min = 100, max = 3000) + @ConfigItem( + keyName = "clueDigDelayMin", + name = "Dig delay min (ms)", + description = "Minimum delay after digging before the next action", + position = 1, + section = easyClueSection + ) + default int clueDigDelayMin() { + return 400; + } + + @Range(min = 100, max = 3000) + @ConfigItem( + keyName = "clueDigDelayMax", + name = "Dig delay max (ms)", + description = "Maximum delay after digging before the next action", + position = 2, + section = easyClueSection + ) + default int clueDigDelayMax() { + return 700; + } + + @Range(min = 50, max = 2000) + @ConfigItem( + keyName = "clueActionDelay", + name = "Action delay (ms)", + description = "Delay between opening scroll boxes, dropping clues, etc.", + position = 3, + section = easyClueSection + ) + default int clueActionDelay() { + return 300; + } + + @ConfigSection( + name = "Hespori (Echo)", + description = "Prayer switching and fight management for Hespori Echo boss", + position = 6, + closedByDefault = true + ) + String hesporiSection = "hesporiSection"; + + @ConfigItem( + keyName = "enableHespori", + name = "Enable", + description = "Manages the Hespori Echo fight. Auto-switches between Protect from Magic and Missiles " + + "based on attack animation, kills flowers with correct combat style based on their overhead prayer, " + + "dodges vine attacks. Uses a fast 150ms loop. " + + "Bring both a melee weapon and a ranged/magic weapon for flower phases.", + position = 0, + section = hesporiSection + ) + default boolean enableHespori() { + return false; + } + + @ConfigItem( + keyName = "hesporiPrayerOnly", + name = "Prayer/eat only", + description = "Only handle prayer switching, eating, and dodging. " + + "You control attacking and flower killing manually.", + position = 1, + section = hesporiSection + ) + default boolean hesporiPrayerOnly() { + return false; + } + + @ConfigItem( + keyName = "hesporiMainWeapon", + name = "Main weapon (boss)", + description = "Weapon to fight the boss with. Echo Hespori is weak to slash (e.g. Abyssal tentacle)", + position = 1, + section = hesporiSection + ) + default String hesporiMainWeapon() { + return "Abyssal tentacle"; + } + + @ConfigItem( + keyName = "hesporiMeleeWeapon", + name = "Melee weapon (flowers)", + description = "Weapon for flowers praying ranged+magic. Can be same as main weapon if main is melee.", + position = 2, + section = hesporiSection + ) + default String hesporiMeleeWeapon() { + return "Abyssal tentacle"; + } + + @ConfigItem( + keyName = "hesporiMageWeapon", + name = "Magic/Ranged weapon (flowers)", + description = "Weapon for flowers praying melee (e.g. Trident of the seas, Magic shortbow)", + position = 3, + section = hesporiSection + ) + default String hesporiMageWeapon() { + return "Trident of the seas"; + } + + @ConfigSection( + name = "Kraken Boss", + description = "Automates the Kraken boss fight — disturb tentacles, wake boss, attack, repeat", + position = 7, + closedByDefault = true + ) + String krakenSection = "krakenSection"; + + @ConfigItem( + keyName = "enableKraken", + name = "Enable", + description = "Automates the Kraken boss fight. Keeps Protect from Magic on, disturbs " + + "all 4 tentacle whirlpools, wakes the boss, attacks until dead, repeats. " + + "Requires a Kraken slayer task and magic combat. Start inside the boss room.", + position = 0, + section = krakenSection + ) + default boolean enableKraken() { + return false; + } + + @Range(min = 10, max = 90) + @ConfigItem( + keyName = "krakenEatAt", + name = "Eat at HP %", + description = "Eat food when HP drops below this percentage", + position = 1, + section = krakenSection + ) + default int krakenEatAt() { + return 50; + } + + @ConfigItem( + keyName = "krakenDrinkPrayer", + name = "Drink prayer pots", + description = "Automatically drink prayer potions", + position = 2, + section = krakenSection + ) + default boolean krakenDrinkPrayer() { + return true; + } + + @Range(min = 1, max = 99) + @ConfigItem( + keyName = "krakenPrayerThreshold", + name = "Drink prayer at", + description = "Drink prayer potion when points drop below this", + position = 3, + section = krakenSection + ) + default int krakenPrayerThreshold() { + return 20; + } + + @ConfigSection( + name = "Ourania Altar", + description = "Craft runes at the Ourania (ZMI) Altar with briefcase banking and transmutation", + position = 8, + closedByDefault = true + ) + String ouraniaSection = "ouraniaSection"; + + @ConfigItem( + keyName = "enableOurania", + name = "Enable", + description = "Crafts runes at Ourania Altar. Banks via briefcase Last-destination to Eniola. " + + "Transmutation runs concurrently to convert air runes to noted pure essence. " + + "Keep air runes, briefcase (equipped), and transmutation ledger in inventory. " + + "Eniola requires runes for banking (auto-pay).", + position = 0, + section = ouraniaSection + ) + default boolean enableOurania() { + return false; + } + + @Range(min = 1, max = 100) + @ConfigItem( + keyName = "ouraniaEatAtPercent", + name = "Eat at HP %", + description = "Eat food from bank when HP drops below this percentage", + position = 1, + section = ouraniaSection + ) + default int ouraniaEatAtPercent() { + return 50; + } + + @ConfigItem( + keyName = "ouraniaFoodName", + name = "Food name", + description = "Name of food to withdraw and eat at bank (e.g. Salmon, Lobster)", + position = 2, + section = ouraniaSection + ) + default String ouraniaFoodName() { + return "Salmon"; + } + + @ConfigSection( + name = "Transmutation", + description = "Casts Alchemic Divergence/Convergence to upgrade or downgrade noted items through tiers", + position = 9, + closedByDefault = true + ) + String transmuteSection = "transmuteSection"; + + @ConfigItem( + keyName = "enableTransmute", + name = "Enable transmutation", + description = "Have the starting noted items in your inventory before enabling. " + + "The script casts the spell on each tier until it reaches the target. " + + "Requires the Transmutation relic and the transmutation ledger equipped or in inventory.", + position = 0, + section = transmuteSection + ) + default boolean enableTransmute() { + return false; + } + + @ConfigItem( + keyName = "transmuteStartItem", + name = "Starting item", + description = "The item you currently have noted in your inventory. Must be in the same category as the target.", + position = 1, + section = transmuteSection + ) + default TransmuteItem transmuteStartItem() { + return TransmuteItem.IRON_ORE; + } + + @ConfigItem( + keyName = "transmuteTargetItem", + name = "Target item", + description = "The final item you want. Must be in the same category as the starting item.", + position = 2, + section = transmuteSection + ) + default TransmuteItem transmuteTargetItem() { + return TransmuteItem.RUNITE_ORE; + } + + @ConfigItem( + keyName = "transmuteDirection", + name = "Direction", + description = "Upgrade (Alchemic Divergence / High Alch) or Downgrade (Alchemic Convergence / Low Alch)", + position = 4, + section = transmuteSection + ) + default TransmuteDirection transmuteDirection() { + return TransmuteDirection.UPGRADE; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java new file mode 100644 index 0000000000..6c907a5dd1 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -0,0 +1,106 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.OverheadTextChanged; +import net.runelite.api.events.ProjectileMoved; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.DV + "Leagues Toolkit", + description = "Quality-of-life utilities for Leagues (anti-AFK, and more to come)", + tags = {"leagues", "microbot", "utility", "afk"}, + version = LeaguesToolkitPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class LeaguesToolkitPlugin extends Plugin { + public static final String version = "1.3.0"; + + @Inject + private LeaguesToolkitConfig config; + + @Inject + private LeaguesToolkitScript leaguesToolkitScript; + + @Provides + LeaguesToolkitConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(LeaguesToolkitConfig.class); + } + + @Override + protected void startUp() { + leaguesToolkitScript.run(config); + } + + @Override + protected void shutDown() { + leaguesToolkitScript.shutdown(); + } + + @Subscribe + public void onAnimationChanged(AnimationChanged event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onAnimationChanged(event); + } + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onHitsplatApplied(event); + } + } + + @Subscribe + public void onOverheadTextChanged(OverheadTextChanged event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onOverheadTextChanged(event); + } + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + if (!config.enableHespori() || config.hesporiPrayerOnly()) return; + int id = event.getProjectile().getId(); + // Vine/quadrant explosion projectile — calculate dodge position on client thread + if (id == 3680) { + var helper = leaguesToolkitScript.getHesporiBossHelper(); + if (!helper.isVineDetected()) { + log.info("[LeaguesToolkit] Vine 3680 — calculating dodge position"); + // Calculate dodge canvas position (we're on client thread, safe to access) + var player = net.runelite.client.plugins.microbot.Microbot.getClient().getLocalPlayer(); + if (player != null) { + var myLocal = player.getLocalLocation(); + // Boss is always at center of arena: LocalPoint(7104, 7104) + // Determine which quadrant player is in relative to boss + // and move to the OPPOSITE quadrant + int bossX = 7104, bossY = 7104; + int dx = (myLocal.getX() > bossX) ? -768 : 768; // If east of boss → go west + int dy = (myLocal.getY() > bossY) ? -768 : 768; // If north of boss → go south + var dodgeLocal = new net.runelite.api.coords.LocalPoint( + myLocal.getX() + dx, myLocal.getY() + dy, myLocal.getWorldView()); + var poly = net.runelite.api.Perspective.getCanvasTilePoly( + net.runelite.client.plugins.microbot.Microbot.getClient(), dodgeLocal); + if (poly != null) { + var bounds = poly.getBounds(); + helper.setDodgeX((int) bounds.getCenterX()); + helper.setDodgeY((int) bounds.getCenterY()); + helper.setVineDetected(true); // Set AFTER coordinates are stored + log.info("[LeaguesToolkit] Dodge position: ({}, {})", bounds.getCenterX(), bounds.getCenterY()); + } + } + } + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java new file mode 100644 index 0000000000..b20e44f9ca --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -0,0 +1,199 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.awt.event.KeyEvent; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LeaguesToolkitScript extends Script { + + private static final int[] ARROW_KEYS = { + KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN + }; + + @Getter + private final DemonicGorillaPrayerHelper gorillaPrayerHelper = new DemonicGorillaPrayerHelper(); + @Getter + private final GemCutter gemCutter = new GemCutter(); + @Getter + private final Transmuter transmuter = new Transmuter(); + @Getter + private final WealthyCitizenThiever wealthyCitizenThiever = new WealthyCitizenThiever(); + @Getter + private final EasyClueOpener easyClueOpener = new EasyClueOpener(); + @Getter + private final HesporiBossHelper hesporiBossHelper = new HesporiBossHelper(); + @Getter + private final KrakenBossHelper krakenBossHelper = new KrakenBossHelper(); + @Getter + private final OuraniaRunner ouraniaRunner = new OuraniaRunner(); + @Getter + private final SnapeGrassTelegrabber snapeGrassTelegrabber = new SnapeGrassTelegrabber(); + + private boolean gorillaPrayerWasEnabled = false; + private boolean gemCutterWasEnabled = false; + private boolean thievingWasEnabled = false; + private boolean easyClueWasEnabled = false; + private boolean hesporiWasEnabled = false; + private boolean krakenWasEnabled = false; + private boolean ouraniaWasEnabled = false; + private boolean snapeGrassWasEnabled = false; + private boolean transmuteWasEnabled = false; + + public boolean run(LeaguesToolkitConfig config) { + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + + if (config.enableAntiAfk()) { + runAntiAfk(config); + } + + if (config.enableGorillaPrayer()) { + if (!gorillaPrayerWasEnabled) { + gorillaPrayerHelper.reset(); + gorillaPrayerHelper.setActive(true); + gorillaPrayerWasEnabled = true; + } + // Fully event-driven — no tick/poll needed + } else { + if (gorillaPrayerWasEnabled) { + gorillaPrayerHelper.setActive(false); + } + gorillaPrayerWasEnabled = false; + } + + if (config.enableGemCutter()) { + if (!gemCutterWasEnabled) { + gemCutter.reset(); + gemCutterWasEnabled = true; + log.info("[LeaguesToolkit] Gem cutter enabled — state: {}", gemCutter.getState()); + } + gemCutter.tick(config); + } else { + gemCutterWasEnabled = false; + } + + if (config.enableThieving()) { + if (!thievingWasEnabled) { + wealthyCitizenThiever.reset(); + thievingWasEnabled = true; + } + wealthyCitizenThiever.tick(config); + } else { + thievingWasEnabled = false; + } + + if (config.enableEasyClue()) { + if (!easyClueWasEnabled) { + easyClueOpener.reset(); + easyClueWasEnabled = true; + } + easyClueOpener.tick(config); + } else { + easyClueWasEnabled = false; + } + + if (config.enableHespori()) { + if (!hesporiWasEnabled) { + hesporiBossHelper.start(config); + hesporiWasEnabled = true; + } + // Combat runs from the main loop (safe thread for doInvoke) + if (!config.hesporiPrayerOnly()) { + hesporiBossHelper.tickCombat(); + } + } else { + if (hesporiWasEnabled) { + hesporiBossHelper.stop(); + } + hesporiWasEnabled = false; + } + + if (config.enableKraken()) { + if (!krakenWasEnabled) { + krakenBossHelper.reset(); + krakenWasEnabled = true; + } + krakenBossHelper.tick(config); + } else { + krakenWasEnabled = false; + } + + if (config.enableOurania()) { + if (!ouraniaWasEnabled) { + ouraniaRunner.reset(); + ouraniaWasEnabled = true; + } + ouraniaRunner.tick(config); + } else { + ouraniaWasEnabled = false; + } + + if (config.enableSnapeGrass()) { + if (!snapeGrassWasEnabled) { + snapeGrassTelegrabber.reset(); + snapeGrassWasEnabled = true; + } + snapeGrassTelegrabber.tick(config); + } else { + snapeGrassWasEnabled = false; + } + + if (config.enableTransmute()) { + if (!transmuteWasEnabled) { + transmuter.reset(); + transmuteWasEnabled = true; + } + if (!transmuter.tick(config)) { + // Transmuter finished or errored — keep running plugin but stop transmuting + log.info("Transmuter stopped: {}", transmuter.getStatus()); + } + } else { + transmuteWasEnabled = false; + } + } catch (Exception ex) { + log.error("LeaguesToolkitScript loop error", ex); + } + }, 0, 1000, TimeUnit.MILLISECONDS); + return true; + } + + private void runAntiAfk(LeaguesToolkitConfig config) { + int minBuffer = Math.min(config.antiAfkBufferMin(), config.antiAfkBufferMax()); + int maxBuffer = Math.max(config.antiAfkBufferMin(), config.antiAfkBufferMax()); + long buffer = Rs2Random.between(minBuffer, maxBuffer); + + if (!Rs2Player.checkIdleLogout(buffer)) return; + + switch (config.antiAfkMethod()) { + case BACKSPACE: + Rs2Keyboard.keyPress(KeyEvent.VK_BACK_SPACE); + break; + case CAMERA_ROTATION: + Rs2Camera.setYaw(Rs2Random.between(0, 2047)); + break; + case RANDOM_ARROW_KEY: + default: + Rs2Keyboard.keyPress(ARROW_KEYS[Rs2Random.between(0, ARROW_KEYS.length - 1)]); + break; + } + } + + @Override + public void shutdown() { + super.shutdown(); + gorillaPrayerHelper.setActive(false); + hesporiBossHelper.stop(); + transmuter.reset(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java new file mode 100644 index 0000000000..71ed44ef16 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java @@ -0,0 +1,253 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class OuraniaRunner { + + private static final WorldArea ALTAR_AREA = new WorldArea(new WorldPoint(3054, 5574, 0), 12, 12); + private static final WorldPoint ENIOLA_AREA = new WorldPoint(3014, 5625, 0); + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + private static final int PURE_ESSENCE_ID = 7936; // Pure essence (BLANKRUNE_HIGH in gameval) + private static final String PURE_ESSENCE_NAME = "Pure essence"; + + private enum State { + CRAFTING, + TELEPORTING_TO_BANK, + BANKING, + WALKING_TO_ALTAR + } + + @Getter + private String status = "Idle"; + private State state = State.CRAFTING; + private int bankFailCount = 0; + private static final int MAX_BANK_FAILS = 3; + + public void reset() { + status = "Idle"; + state = State.CRAFTING; + bankFailCount = 0; + } + + public boolean tick(LeaguesToolkitConfig config) { + switch (state) { + case CRAFTING: + return handleCrafting(); + case TELEPORTING_TO_BANK: + return handleTeleportToBank(); + case BANKING: + return handleBanking(config); + case WALKING_TO_ALTAR: + return handleWalkingToAltar(config); + } + return true; + } + + private boolean handleCrafting() { + // Check if we have pure essence to craft + if (!Rs2Inventory.hasItem(PURE_ESSENCE_ID)) { + if (isNearAltar()) { + status = "No essence — heading to bank"; + state = State.TELEPORTING_TO_BANK; + return true; + } else if (isNearEniola()) { + status = "At bank, no essence"; + state = State.BANKING; + return true; + } else { + status = "No essence — teleporting to bank"; + state = State.TELEPORTING_TO_BANK; + return true; + } + } + + if (!isNearAltar()) { + status = "Walking to altar"; + state = State.WALKING_TO_ALTAR; + return true; + } + + if (Rs2Player.isAnimating()) { + status = "Crafting..."; + return true; + } + + status = "Crafting runes at altar"; + Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR) + .interact("craft-rune"); + Rs2Inventory.waitForInventoryChanges(5000); + return true; + } + + private boolean handleTeleportToBank() { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + status = "In transit..."; + return true; + } + + if (isNearEniola()) { + status = "At Eniola"; + state = State.BANKING; + return true; + } + + // Use briefcase to teleport to bank + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + status = "Briefcase Last-destination to Eniola"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 10000); + sleep(500, 1000); + if (isNearEniola()) { + state = State.BANKING; + } + } else { + // No briefcase — walk to Eniola + status = "No briefcase — walking to Eniola"; + log.warn("[Ourania] Briefcase not equipped, falling back to walk"); + Rs2Walker.walkTo(ENIOLA_AREA, 4); + sleepUntil(() -> isNearEniola() || !Rs2Player.isMoving(), 30000); + if (isNearEniola()) { + state = State.BANKING; + } + } + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (!Rs2Bank.isOpen()) { + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query() + .withId(NpcID.RC_ZMI_BANKER).nearest(); + if (eniola == null) { + status = "Can't find Eniola — walking closer"; + Rs2Walker.walkTo(ENIOLA_AREA, 4); + sleepUntil(() -> !Rs2Player.isMoving(), 5000); + return true; + } + status = "Opening bank at Eniola"; + eniola.click("bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + + // Auto-pay exhaustion detection — if bank didn't open after clicking, runes may be depleted + if (!Rs2Bank.isOpen()) { + bankFailCount++; + log.warn("[Ourania] Bank failed to open (attempt {}/{})", bankFailCount, MAX_BANK_FAILS); + if (bankFailCount >= MAX_BANK_FAILS) { + status = "Eniola refused banking — auto-pay runes depleted?"; + log.error("[Ourania] Stopping: Eniola refused banking {} times — check auto-pay runes", MAX_BANK_FAILS); + return false; + } + sleep(1000, 2000); + return true; + } + bankFailCount = 0; + return true; + } + + // Deposit crafted runes only — keep air runes, noted pure essence, ledger, etc. + status = "Depositing crafted runes"; + // Collect IDs to deposit first, then deposit (avoid modifying stream mid-iteration) + java.util.List toDeposit = Rs2Inventory.items() + .filter(item -> item.getName().toLowerCase().contains("rune") + && !item.getName().toLowerCase().contains("air rune") + && !item.getName().toLowerCase().contains("pure essence")) + .map(item -> item.getId()) + .distinct() + .collect(java.util.stream.Collectors.toList()); + + for (int id : toDeposit) { + Rs2Bank.depositAll(id); + Rs2Inventory.waitForInventoryChanges(1800); + } + + // Eat food if HP low + if (Rs2Player.getHealthPercentage() <= config.ouraniaEatAtPercent()) { + status = "Eating food at bank"; + int maxEats = 10; + while (--maxEats > 0 && Rs2Player.getHealthPercentage() < 100 && Rs2Bank.hasItem(config.ouraniaFoodName())) { + Rs2Bank.withdrawOne(config.ouraniaFoodName()); + Rs2Inventory.waitForInventoryChanges(1800); + Rs2Player.useFood(); + Rs2Inventory.waitForInventoryChanges(1800); + } + } + + // If no pure essence in bank, deposit our noted stack so it becomes available unnoted + if (!Rs2Bank.hasBankItem(PURE_ESSENCE_NAME)) { + if (Rs2Inventory.hasItem(PURE_ESSENCE_NAME)) { + status = "Depositing noted pure essence to unnote"; + Rs2Bank.depositAll(PURE_ESSENCE_ID); + Rs2Inventory.waitForInventoryChanges(1800); + } else { + status = "No pure essence anywhere — waiting for transmutation"; + Rs2Bank.closeBank(); + sleep(3000, 5000); + return true; + } + } + + status = "Withdrawing pure essence"; + Rs2Bank.withdrawAll(PURE_ESSENCE_ID); + Rs2Inventory.waitForInventoryChanges(1800); + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + state = State.WALKING_TO_ALTAR; + return true; + } + + private boolean handleWalkingToAltar(LeaguesToolkitConfig config) { + if (isNearAltar()) { + state = State.CRAFTING; + return true; + } + + if (Rs2Player.isMoving()) { + status = "Walking to altar..."; + // Eat while walking if HP low + if (Rs2Player.getHealthPercentage() <= config.ouraniaEatAtPercent()) { + if (Rs2Inventory.hasItem(config.ouraniaFoodName())) { + Rs2Player.useFood(); + } + } + return true; + } + + status = "Walking to altar"; + // Walk via the short path through the cave + Rs2Walker.walkTo(new WorldPoint(3060, 5580, 0), 6); + sleep(2000, 3000); + return true; + } + + private boolean isNearAltar() { + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && ALTAR_AREA.contains(loc); + } + + private boolean isNearEniola() { + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query() + .withId(NpcID.RC_ZMI_BANKER).nearest(); + if (eniola == null) return false; + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && loc.distanceTo2D(eniola.getWorldLocation()) < 12; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java new file mode 100644 index 0000000000..a5f3e39041 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java @@ -0,0 +1,145 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class SnapeGrassTelegrabber { + + private static final WorldPoint SNAPE_GRASS_LOCATION = new WorldPoint(1736, 3170, 0); + private static final int SNAPE_GRASS_ITEM_ID = 231; + private static final int REQUIRED_MAGIC_LEVEL = 33; + private static final int GRAB_RANGE = 15; + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + private static final int SNAPE_GRASS_NOTED_ID = 232; + + private boolean banking = false; + + @Getter + private String status = "Idle"; + + public void reset() { + status = "Idle"; + banking = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Handle banking if inventory was full + if (banking) { + return handleBanking(config); + } + // Check magic level + if (!Rs2Player.getSkillRequirement(Skill.MAGIC, REQUIRED_MAGIC_LEVEL)) { + status = "Need 33 Magic for Telekinetic Grab"; + return false; + } + + // Check for runes — need law runes + air runes (or air staff) + boolean hasLawRunes = Rs2Inventory.hasItem("Law rune"); + boolean hasAirSource = Rs2Inventory.hasItem("Air rune") + || Rs2Equipment.isWearing("Staff of air") + || Rs2Equipment.isWearing("Air battlestaff") + || Rs2Equipment.isWearing("Mystic air staff"); + + if (!hasLawRunes) { + status = "No law runes in inventory"; + return false; + } + if (!hasAirSource) { + status = "No air runes or air staff equipped"; + return false; + } + + // Check if inventory is full — bank and come back + if (Rs2Inventory.isFull()) { + banking = true; + return handleBanking(config); + } + + // Walk to location if not nearby + WorldPoint playerPos = Rs2Player.getWorldLocation(); + if (playerPos == null) return true; + + int distance = playerPos.distanceTo(SNAPE_GRASS_LOCATION); + if (distance > 20) { + status = "Walking to snape grass (" + distance + " tiles)"; + Rs2Walker.walkTo(SNAPE_GRASS_LOCATION, 4); + sleep(3000, 5000); + return true; + } + + // Wait if still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + status = "Moving..."; + return true; + } + + // Check if snape grass is on the ground + if (!Rs2GroundItem.exists(SNAPE_GRASS_ITEM_ID, GRAB_RANGE)) { + status = "Waiting for snape grass to respawn..."; + return true; + } + + // Cast Telekinetic Grab + status = "Casting Telekinetic Grab on snape grass"; + if (!Rs2Magic.cast(MagicAction.TELEKINETIC_GRAB)) { + status = "Failed to cast — check spellbook"; + return true; + } + sleep(300, 500); + + // Click the ground item + Rs2GroundItem.interact(SNAPE_GRASS_ITEM_ID, "Cast", GRAB_RANGE); + sleep(600, 900); + + // Wait for the grab animation to finish + sleepUntil(() -> !Rs2Player.isAnimating(), 5000); + sleep(300, 600); + + status = "Grabbed snape grass"; + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (!Rs2Bank.isOpen()) { + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + status = "Teleporting to bank via briefcase"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 8000); + sleep(500, 1000); + status = "Opening bank"; + Rs2Bank.openBank(); + sleepUntil(Rs2Bank::isOpen, 5000); + } else { + status = "Walking to nearest bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + } + return true; + } + + status = "Depositing snape grass"; + Rs2Bank.depositAll("Snape grass"); + sleep(300, 500); + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + banking = false; + status = "Walking back to snape grass"; + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java new file mode 100644 index 0000000000..2dd474bffa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java @@ -0,0 +1,69 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; + +@Getter +@RequiredArgsConstructor +public enum TransmuteCategory { + ORES("Ores", Arrays.asList( + "Tin ore", "Copper ore", "Iron ore", "Coal", "Mithril ore", "Adamantite ore", "Runite ore" + )), + FISH("Fish", Arrays.asList( + "Raw shrimps", "Raw sardine", "Raw herring", "Raw mackerel", "Raw trout", + "Raw cod", "Raw pike", "Raw salmon", "Raw tuna", "Raw lobster", + "Raw bass", "Raw swordfish", "Raw karambwan", "Raw shark", "Raw anglerfish" + )), + GEMS("Gems", Arrays.asList( + "Uncut sapphire", "Uncut emerald", "Uncut ruby", "Uncut diamond", + "Uncut opal", "Uncut jade", "Uncut red topaz", "Uncut dragonstone" + )), + RUNES("Runes", Arrays.asList( + "Air rune", "Water rune", "Earth rune", "Fire rune", + "Chaos rune", "Nature rune", "Cosmic rune", "Law rune", + "Death rune", "Astral rune", "Blood rune", "Soul rune", "Wrath rune" + )), + ASHES("Ashes", Arrays.asList( + "Ashes", "Volcanic ash", "Fiendish ashes", "Vile ashes", + "Malicious ashes", "Abyssal ashes", "Infernal ashes" + )), + COMPOST("Compost", Arrays.asList( + "Compost", "Supercompost", "Ultracompost" + )), + LOGS("Logs", Arrays.asList( + "Logs", "Oak logs", "Willow logs", "Teak logs", "Maple logs", + "Mahogany logs", "Yew logs", "Magic logs", "Redwood logs" + )), + BONES("Bones", Arrays.asList( + "Bones", "Bat bones", "Big bones", "Wyrmling bones", "Baby dragon bones", + "Wyrm bones", "Dragon bones", "Drake bones", "Lava dragon bones", + "Hydra bones", "Dagannoth bones", "Superior dragon bones" + )), + HIDES("Hides", Arrays.asList( + "Cowhide", "Snakeskin", "Green dragonhide", "Blue dragonhide", + "Red dragonhide", "Black dragonhide" + )); + + private final String displayName; + private final List chain; + + public int indexOf(String itemName) { + for (int i = 0; i < chain.size(); i++) { + if (chain.get(i).equalsIgnoreCase(itemName)) return i; + } + return -1; + } + + public String getItem(int index) { + if (index < 0 || index >= chain.size()) return null; + return chain.get(index); + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java new file mode 100644 index 0000000000..46cdf9f42c --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java @@ -0,0 +1,6 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum TransmuteDirection { + UPGRADE, + DOWNGRADE +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java new file mode 100644 index 0000000000..47bfc43d40 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java @@ -0,0 +1,114 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum TransmuteItem { + // Ores + TIN_ORE("Tin ore", TransmuteCategory.ORES), + COPPER_ORE("Copper ore", TransmuteCategory.ORES), + IRON_ORE("Iron ore", TransmuteCategory.ORES), + COAL("Coal", TransmuteCategory.ORES), + MITHRIL_ORE("Mithril ore", TransmuteCategory.ORES), + ADAMANTITE_ORE("Adamantite ore", TransmuteCategory.ORES), + RUNITE_ORE("Runite ore", TransmuteCategory.ORES), + + // Fish + RAW_SHRIMPS("Raw shrimps", TransmuteCategory.FISH), + RAW_SARDINE("Raw sardine", TransmuteCategory.FISH), + RAW_HERRING("Raw herring", TransmuteCategory.FISH), + RAW_MACKEREL("Raw mackerel", TransmuteCategory.FISH), + RAW_TROUT("Raw trout", TransmuteCategory.FISH), + RAW_COD("Raw cod", TransmuteCategory.FISH), + RAW_PIKE("Raw pike", TransmuteCategory.FISH), + RAW_SALMON("Raw salmon", TransmuteCategory.FISH), + RAW_TUNA("Raw tuna", TransmuteCategory.FISH), + RAW_LOBSTER("Raw lobster", TransmuteCategory.FISH), + RAW_BASS("Raw bass", TransmuteCategory.FISH), + RAW_SWORDFISH("Raw swordfish", TransmuteCategory.FISH), + RAW_KARAMBWAN("Raw karambwan", TransmuteCategory.FISH), + RAW_SHARK("Raw shark", TransmuteCategory.FISH), + RAW_ANGLERFISH("Raw anglerfish", TransmuteCategory.FISH), + + // Gems (transmute order: sapphire → emerald → ruby → diamond → opal → jade → red topaz → dragonstone) + UNCUT_SAPPHIRE("Uncut sapphire", TransmuteCategory.GEMS), + UNCUT_EMERALD("Uncut emerald", TransmuteCategory.GEMS), + UNCUT_RUBY("Uncut ruby", TransmuteCategory.GEMS), + UNCUT_DIAMOND("Uncut diamond", TransmuteCategory.GEMS), + UNCUT_OPAL("Uncut opal", TransmuteCategory.GEMS), + UNCUT_JADE("Uncut jade", TransmuteCategory.GEMS), + UNCUT_RED_TOPAZ("Uncut red topaz", TransmuteCategory.GEMS), + UNCUT_DRAGONSTONE("Uncut dragonstone", TransmuteCategory.GEMS), + + // Runes + AIR_RUNE("Air rune", TransmuteCategory.RUNES), + WATER_RUNE("Water rune", TransmuteCategory.RUNES), + EARTH_RUNE("Earth rune", TransmuteCategory.RUNES), + FIRE_RUNE("Fire rune", TransmuteCategory.RUNES), + CHAOS_RUNE("Chaos rune", TransmuteCategory.RUNES), + NATURE_RUNE("Nature rune", TransmuteCategory.RUNES), + COSMIC_RUNE("Cosmic rune", TransmuteCategory.RUNES), + LAW_RUNE("Law rune", TransmuteCategory.RUNES), + DEATH_RUNE("Death rune", TransmuteCategory.RUNES), + ASTRAL_RUNE("Astral rune", TransmuteCategory.RUNES), + BLOOD_RUNE("Blood rune", TransmuteCategory.RUNES), + SOUL_RUNE("Soul rune", TransmuteCategory.RUNES), + WRATH_RUNE("Wrath rune", TransmuteCategory.RUNES), + + // Logs + LOGS("Logs", TransmuteCategory.LOGS), + OAK_LOGS("Oak logs", TransmuteCategory.LOGS), + WILLOW_LOGS("Willow logs", TransmuteCategory.LOGS), + TEAK_LOGS("Teak logs", TransmuteCategory.LOGS), + MAPLE_LOGS("Maple logs", TransmuteCategory.LOGS), + MAHOGANY_LOGS("Mahogany logs", TransmuteCategory.LOGS), + YEW_LOGS("Yew logs", TransmuteCategory.LOGS), + MAGIC_LOGS("Magic logs", TransmuteCategory.LOGS), + REDWOOD_LOGS("Redwood logs", TransmuteCategory.LOGS), + + // Bones + BONES("Bones", TransmuteCategory.BONES), + BAT_BONES("Bat bones", TransmuteCategory.BONES), + BIG_BONES("Big bones", TransmuteCategory.BONES), + WYRMLING_BONES("Wyrmling bones", TransmuteCategory.BONES), + BABY_DRAGON_BONES("Baby dragon bones", TransmuteCategory.BONES), + WYRM_BONES("Wyrm bones", TransmuteCategory.BONES), + DRAGON_BONES("Dragon bones", TransmuteCategory.BONES), + DRAKE_BONES("Drake bones", TransmuteCategory.BONES), + LAVA_DRAGON_BONES("Lava dragon bones", TransmuteCategory.BONES), + HYDRA_BONES("Hydra bones", TransmuteCategory.BONES), + DAGANNOTH_BONES("Dagannoth bones", TransmuteCategory.BONES), + SUPERIOR_DRAGON_BONES("Superior dragon bones", TransmuteCategory.BONES), + + // Hides + COWHIDE("Cowhide", TransmuteCategory.HIDES), + SNAKESKIN("Snakeskin", TransmuteCategory.HIDES), + GREEN_DRAGONHIDE("Green dragonhide", TransmuteCategory.HIDES), + BLUE_DRAGONHIDE("Blue dragonhide", TransmuteCategory.HIDES), + RED_DRAGONHIDE("Red dragonhide", TransmuteCategory.HIDES), + BLACK_DRAGONHIDE("Black dragonhide", TransmuteCategory.HIDES), + + // Ashes + ASHES("Ashes", TransmuteCategory.ASHES), + VOLCANIC_ASH("Volcanic ash", TransmuteCategory.ASHES), + FIENDISH_ASHES("Fiendish ashes", TransmuteCategory.ASHES), + VILE_ASHES("Vile ashes", TransmuteCategory.ASHES), + MALICIOUS_ASHES("Malicious ashes", TransmuteCategory.ASHES), + ABYSSAL_ASHES("Abyssal ashes", TransmuteCategory.ASHES), + INFERNAL_ASHES("Infernal ashes", TransmuteCategory.ASHES), + + // Compost + COMPOST("Compost", TransmuteCategory.COMPOST), + SUPERCOMPOST("Supercompost", TransmuteCategory.COMPOST), + ULTRACOMPOST("Ultracompost", TransmuteCategory.COMPOST); + + private final String itemName; + private final TransmuteCategory category; + + @Override + public String toString() { + return itemName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java new file mode 100644 index 0000000000..bec017a625 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java @@ -0,0 +1,229 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; + +import java.util.List; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class Transmuter { + + @Getter + private String status = "Idle"; + @Getter + private boolean running = false; + + // Track state across ticks instead of blocking + private boolean casting = false; + private long lastCastTime = 0; + private int lastItemCount = 0; + private String currentItemName = null; + + // If item count hasn't changed for this long AND shop is closed, re-cast. + // Auto-recast processes 10 items per batch with gaps between, so be patient. + private static final long RECAST_TIMEOUT_MS = 45_000; + // When shop is open, auto-recast pauses entirely — use a much longer timeout + private static final long RECAST_TIMEOUT_SHOP_OPEN_MS = 180_000; + + public void reset() { + status = "Idle"; + running = false; + casting = false; + lastCastTime = 0; + lastItemCount = 0; + currentItemName = null; + } + + /** + * Run one tick of the transmute loop. + * Returns false if the feature should be disabled (done or error). + */ + public boolean tick(LeaguesToolkitConfig config) { + running = true; + TransmuteItem startEnum = config.transmuteStartItem(); + TransmuteItem targetEnum = config.transmuteTargetItem(); + TransmuteDirection direction = config.transmuteDirection(); + + if (startEnum == null || targetEnum == null) { + status = "Config incomplete"; + return false; + } + + if (startEnum.getCategory() != targetEnum.getCategory()) { + status = "Start and target must be in the same category (" + + startEnum.getCategory().getDisplayName() + " vs " + + targetEnum.getCategory().getDisplayName() + ")"; + return false; + } + + TransmuteCategory category = startEnum.getCategory(); + String startItem = startEnum.getItemName(); + String targetItem = targetEnum.getItemName(); + List chain = category.getChain(); + int startIdx = category.indexOf(startItem); + int targetIdx = category.indexOf(targetItem); + + if (startIdx == -1 || targetIdx == -1) { + status = "Item not found in " + category.getDisplayName() + " chain"; + return false; + } + + // Determine direction based on chain positions — the user picks upgrade/downgrade + // to select the spell, but we walk the chain in whichever direction goes from start to target + int step = (targetIdx > startIdx) ? 1 : -1; + int currentIdx = findCurrentTier(chain, startIdx, targetIdx, step); + + // Check if we're done: target exists AND no intermediate items remain + if (Rs2Inventory.hasItem(targetItem)) { + boolean intermediatesRemain = false; + for (int i = startIdx; i != targetIdx; i += step) { + if (i < 0 || i >= chain.size()) break; + if (Rs2Inventory.hasItem(chain.get(i))) { + intermediatesRemain = true; + break; + } + } + if (!intermediatesRemain) { + status = "Done — all items are now " + targetItem; + running = false; + casting = false; + return false; + } + // Target exists but intermediates remain — keep going + } + + if (currentIdx == -1) { + // Log what we searched for to debug + log.info("[Transmuter] No items found. Searched chain indices {} to {} (step {}):", startIdx, targetIdx, step); + for (int i = startIdx; i != targetIdx + step; i += step) { + if (i < 0 || i >= chain.size()) break; + String name = chain.get(i); + boolean has = Rs2Inventory.hasItem(name); + log.info("[Transmuter] {} = {}", name, has); + } + status = "No transmutable items found in inventory"; + running = false; + casting = false; + return false; + } + + String currentItem = chain.get(currentIdx); + String nextItem = chain.get(currentIdx + step); + + // Are we mid-cast and the item changed? Advance. + if (casting && currentItemName != null && !currentItemName.equals(currentItem)) { + log.info("Tier advanced: {} → {}", currentItemName, currentItem); + casting = false; + currentItemName = null; + } + + // Check if auto-recast is still running + if (casting) { + int currentCount = Rs2Inventory.count(currentItem); + + if (currentCount == 0) { + // All items transmuted — next tick will pick up new tier + casting = false; + currentItemName = null; + status = "Tier complete → " + nextItem; + return true; + } + + // Check if count is still decreasing (auto-recast active) + if (currentCount < lastItemCount) { + lastItemCount = currentCount; + lastCastTime = System.currentTimeMillis(); + status = "Auto-recasting " + currentItem + " → " + nextItem + " (" + currentCount + " left)"; + return true; + } + + // Count hasn't changed — check if we've timed out (recast interrupted) + // Shop windows pause auto-recast, so use a longer timeout when shop is open + long elapsed = System.currentTimeMillis() - lastCastTime; + long timeout = Rs2Shop.isOpen() ? RECAST_TIMEOUT_SHOP_OPEN_MS : RECAST_TIMEOUT_MS; + + if (elapsed < timeout) { + String reason = Rs2Shop.isOpen() ? " (shop open — recast paused)" : ""; + status = "Waiting for recast... " + currentItem + " (" + currentCount + " left)" + reason; + return true; + } + + // Timed out — recast got interrupted, try again + log.info("Auto-recast interrupted for {} (shop open: {}), re-casting", currentItem, Rs2Shop.isOpen()); + casting = false; + } + + // Cast the spell on the current tier item + // In Leagues, High Alch = "Alchemic Divergence", Low Alch = "Alchemic Convergence" + String spellName = direction == TransmuteDirection.UPGRADE + ? "Alchemic Divergence" + : "Alchemic Convergence"; + status = "Casting " + spellName + " on " + currentItem + " → " + nextItem; + + Rs2ItemModel item = Rs2Inventory.get(currentItem); + if (item == null) { + status = "Lost " + currentItem + " from inventory"; + return true; + } + + // Switch to magic tab and click the spell by name + Rs2Tab.switchToMagicTab(); + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.MAGIC).orElse(false)); + sleep(200, 400); + + if (!Rs2Widget.clickWidget(spellName)) { + status = "Could not find " + spellName + " spell — do you have the Transmutation relic?"; + return true; + } + + // Wait for inventory tab to appear, then click the item + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY).orElse(false)); + sleep(300, 500); + Rs2Inventory.interact(item, "Cast"); + sleep(600, 900); + + // Switch back to inventory tab so we can monitor item count changes + Rs2Tab.switchToInventoryTab(); + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY).orElse(false)); + + // Mark as casting — subsequent ticks will monitor progress + casting = true; + currentItemName = currentItem; + lastItemCount = Rs2Inventory.count(currentItem); + lastCastTime = System.currentTimeMillis(); + status = "Auto-recasting " + currentItem + " → " + nextItem + " (" + lastItemCount + " remaining)"; + + return true; + } + + /** + * Finds which tier item is currently in the inventory. + * Searches the entire chain (not just start→target) in case a previous + * transmutation went in an unexpected direction. + */ + private int findCurrentTier(List chain, int startIdx, int targetIdx, int step) { + // First: search from start toward target (expected path) + for (int i = startIdx; i != targetIdx + step; i += step) { + if (i < 0 || i >= chain.size()) break; + if (Rs2Inventory.hasItem(chain.get(i))) return i; + } + // Fallback: search the entire chain for any matching item + for (int i = 0; i < chain.size(); i++) { + if (Rs2Inventory.hasItem(chain.get(i))) return i; + } + return -1; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java new file mode 100644 index 0000000000..c8b7f638ba --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java @@ -0,0 +1,77 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class WealthyCitizenThiever { + + private static final String NPC_NAME = "Wealthy citizen"; + private static final String COIN_POUCH = "Coin pouch"; + private static final int COIN_POUCH_ID = 28822; + + @Getter + private String status = "Idle"; + + private boolean pickpocketing = false; + + public void reset() { + status = "Idle"; + pickpocketing = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Check if inventory is full (excluding coin pouches which stack) + if (Rs2Inventory.isFull() && !Rs2Inventory.hasItem(COIN_POUCH)) { + status = "Inventory full"; + return false; + } + + // Open coin pouches if at threshold (use itemQuantity for stack count) + int pouchCount = Rs2Inventory.itemQuantity(COIN_POUCH_ID); + if (pouchCount >= config.coinPouchThreshold()) { + status = "Opening " + pouchCount + " coin pouches"; + pickpocketing = false; + Rs2Inventory.interact(COIN_POUCH, "Open-all"); + sleepUntil(() -> !Rs2Inventory.hasItem(COIN_POUCH), 3000); + sleep(200, 400); + return true; + } + + // If already pickpocketing (Larcenist auto-repickpockets), just monitor + if (pickpocketing && (Rs2Player.isAnimating() || Rs2Player.isInteracting())) { + status = "Pickpocketing... (" + pouchCount + "/" + config.coinPouchThreshold() + " pouches)"; + return true; + } + + // Not actively pickpocketing — click the NPC once to start + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> { + String name = Microbot.getClientThread() + .runOnClientThreadOptional(n::getName).orElse(null); + return NPC_NAME.equalsIgnoreCase(name); + }) + .nearest(); + + if (npc == null) { + status = "No Wealthy citizen found nearby"; + pickpocketing = false; + return true; + } + + status = "Starting pickpocket on Wealthy citizen"; + Rs2Npc.pickpocket(npc.getNpc()); + pickpocketing = true; + sleep(600, 900); + + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java new file mode 100644 index 0000000000..b88d018a0f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java @@ -0,0 +1,202 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Keybind; + +@ConfigGroup("leftclickcast") +public interface LeftClickCastConfig extends Config +{ + @ConfigItem( + keyName = "enabled", + name = "Enabled", + description = "Replace the left-click Attack option on NPCs with Cast Spell", + position = 0 + ) + default boolean enabled() + { + return true; + } + + @ConfigItem( + keyName = "spell", + name = "Spell", + description = "The currently active spell. Synced with the active slot — pressing a slot hotkey updates this, and editing this updates the active slot's spell.", + position = 1 + ) + default PertTargetSpell spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "requireMagicWeapon", + name = "Require magic weapon", + description = "When enabled, the Cast entry is only inserted while a staff, bladed staff, powered staff, or powered wand is equipped. Disable to cast regardless of equipped weapon.", + position = 2 + ) + default boolean requireMagicWeapon() + { + return true; + } + + @ConfigSection( + name = "Spell Slots", + description = "Up to five spells that can be bound to hotkeys for mid-fight swapping.", + position = 10 + ) + String spellSlotsSection = "spellSlots"; + + @ConfigSection( + name = "Hotkeys", + description = "Hotkey bindings that switch the active spell slot.", + position = 11 + ) + String hotkeysSection = "hotkeys"; + + @ConfigItem( + keyName = "slot1Spell", + name = "Slot 1 Spell", + description = "Spell for slot 1 (the startup-active slot).", + section = spellSlotsSection, + position = 0 + ) + default PertTargetSpell slot1Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot2Spell", + name = "Slot 2 Spell", + description = "Spell for slot 2.", + section = spellSlotsSection, + position = 1 + ) + default PertTargetSpell slot2Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot3Spell", + name = "Slot 3 Spell", + description = "Spell for slot 3.", + section = spellSlotsSection, + position = 2 + ) + default PertTargetSpell slot3Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot4Spell", + name = "Slot 4 Spell", + description = "Spell for slot 4.", + section = spellSlotsSection, + position = 3 + ) + default PertTargetSpell slot4Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot5Spell", + name = "Slot 5 Spell", + description = "Spell for slot 5.", + section = spellSlotsSection, + position = 4 + ) + default PertTargetSpell slot5Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "enabledToggleHotkey", + name = "Enable/Disable Hotkey", + description = "Hotkey that toggles the plugin on and off.", + section = hotkeysSection, + position = 0 + ) + default Keybind enabledToggleHotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot1Hotkey", + name = "Slot 1 Hotkey", + description = "Hotkey that activates slot 1.", + section = hotkeysSection, + position = 1 + ) + default Keybind slot1Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot2Hotkey", + name = "Slot 2 Hotkey", + description = "Hotkey that activates slot 2.", + section = hotkeysSection, + position = 2 + ) + default Keybind slot2Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot3Hotkey", + name = "Slot 3 Hotkey", + description = "Hotkey that activates slot 3.", + section = hotkeysSection, + position = 3 + ) + default Keybind slot3Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot4Hotkey", + name = "Slot 4 Hotkey", + description = "Hotkey that activates slot 4.", + section = hotkeysSection, + position = 4 + ) + default Keybind slot4Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot5Hotkey", + name = "Slot 5 Hotkey", + description = "Hotkey that activates slot 5.", + section = hotkeysSection, + position = 5 + ) + default Keybind slot5Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "chatFeedback", + name = "Chat feedback", + description = "Post a game chat message on plugin events (active slot change, enable/disable toggle).", + section = hotkeysSection, + position = 6 + ) + default boolean chatFeedback() + { + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java new file mode 100644 index 0000000000..a5df550241 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java @@ -0,0 +1,478 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import com.google.inject.Provides; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.EnumComposition; +import net.runelite.api.EnumID; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Menu; +import net.runelite.api.NPC; +import net.runelite.api.ParamID; +import net.runelite.api.Player; +import net.runelite.api.StructComposition; +import net.runelite.api.events.PostMenuSort; +import net.runelite.api.gameval.VarbitID; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.Keybind; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.ExternalPluginsChanged; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.api.widgets.Widget; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; +import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; +import net.runelite.client.util.HotkeyListener; + +@PluginDescriptor( + name = PluginConstants.PERT + "Left-Click Cast", + description = "Replaces left-click Attack on NPCs with a preconfigured Cast Spell action.", + tags = {"magic", "combat", "spell", "left-click", "cast", "pvm", "pvp"}, + authors = {"Pert"}, + version = LeftClickCastPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +public class LeftClickCastPlugin extends Plugin +{ + static final String version = "1.3.2"; + + private static final int SLOT_COUNT = 5; + + @Inject + private Client client; + + @Inject + private LeftClickCastConfig config; + + @Inject + private KeyManager keyManager; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private ConfigManager configManager; + + @Inject + private EventBus eventBus; + + private volatile int activeSlot = 0; + + private final HotkeyListener[] hotkeyListeners = new HotkeyListener[SLOT_COUNT]; + + private HotkeyListener enabledToggleListener; + + @Provides + LeftClickCastConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(LeftClickCastConfig.class); + } + + @Override + protected void startUp() + { + // MicrobotConfigPanel renders boolean checkboxes from raw stored values; missing keys read as false + // even when the @ConfigItem default is true. Materialize defaults so the UI and the proxy agree. + configManager.setDefaultConfiguration(config, false); + activeSlot = 0; + for (int i = 0; i < SLOT_COUNT; i++) + { + final int slotIndex = i; + HotkeyListener listener = new HotkeyListener(() -> slotHotkeyFor(slotIndex)) + { + @Override + public void hotkeyPressed() + { + onSlotHotkey(slotIndex); + } + }; + hotkeyListeners[i] = listener; + keyManager.registerKeyListener(listener); + } + enabledToggleListener = new HotkeyListener(() -> config.enabledToggleHotkey()) + { + @Override + public void hotkeyPressed() + { + onEnabledToggleHotkey(); + } + }; + keyManager.registerKeyListener(enabledToggleListener); + migrateLegacySpellKey(); + syncTopSpellToActiveSlot(); + } + + @Override + protected void shutDown() + { + for (int i = 0; i < hotkeyListeners.length; i++) + { + HotkeyListener listener = hotkeyListeners[i]; + if (listener != null) + { + keyManager.unregisterKeyListener(listener); + hotkeyListeners[i] = null; + } + } + if (enabledToggleListener != null) + { + keyManager.unregisterKeyListener(enabledToggleListener); + enabledToggleListener = null; + } + } + + @Subscribe + public void onPostMenuSort(PostMenuSort event) + { + // Don't mutate while the right-click menu is open — entries are frozen at open-time. + if (client.isMenuOpen()) + { + return; + } + if (!config.enabled()) + { + return; + } + PertTargetSpell spell = slotSpellFor(activeSlot); + if (spell == null) + { + return; + } + if (config.requireMagicWeapon() && !isMagicWeaponEquipped()) + { + return; + } + + Menu menu = client.getMenu(); + MenuEntry[] entries = menu.getMenuEntries(); + + // Find the top-most NPC or Player Attack entry (the game's already-sorted left-click candidate). + int attackIdx = -1; + Actor targetActor = null; + for (int i = entries.length - 1; i >= 0; i--) + { + MenuEntry e = entries[i]; + if (!"Attack".equals(e.getOption())) + { + continue; + } + if (e.getNpc() != null) + { + attackIdx = i; + targetActor = e.getNpc(); + break; + } + if (e.getPlayer() != null) + { + attackIdx = i; + targetActor = e.getPlayer(); + break; + } + } + if (attackIdx < 0) + { + return; + } + + MenuEntry attack = entries[attackIdx]; + final Actor dispatchTarget = targetActor; + final PertTargetSpell dispatchSpell = spell; + + // Append a new RUNELITE-type "Cast X" entry at the tail. The tail is the left-click action in + // RuneLite's menu model, so Cast becomes left-click while the original Attack entry stays in the + // list — preserving right-click "Attack" access. Identifier/param0/param1/worldViewId are copied + // from the original so target highlighting behaves the same as a real Attack hover. + MenuEntry cast = menu.createMenuEntry(-1) + .setOption("Cast " + dispatchSpell.getDisplayName()) + .setTarget(attack.getTarget()) + .setType(MenuAction.RUNELITE) + .setIdentifier(attack.getIdentifier()) + .setParam0(attack.getParam0()) + .setParam1(attack.getParam1()) + .onClick(e -> castOnTargetFast(dispatchSpell, dispatchTarget)); + cast.setWorldViewId(attack.getWorldViewId()); + } + + private Keybind slotHotkeyFor(int index) + { + switch (index) + { + case 0: + return config.slot1Hotkey(); + case 1: + return config.slot2Hotkey(); + case 2: + return config.slot3Hotkey(); + case 3: + return config.slot4Hotkey(); + case 4: + return config.slot5Hotkey(); + default: + return Keybind.NOT_SET; + } + } + + private PertTargetSpell slotSpellFor(int index) + { + switch (index) + { + case 0: + return config.slot1Spell(); + case 1: + return config.slot2Spell(); + case 2: + return config.slot3Spell(); + case 3: + return config.slot4Spell(); + case 4: + return config.slot5Spell(); + default: + return config.slot1Spell(); + } + } + + private static String slotSpellKeyFor(int index) + { + switch (index) + { + case 0: + return "slot1Spell"; + case 1: + return "slot2Spell"; + case 2: + return "slot3Spell"; + case 3: + return "slot4Spell"; + case 4: + return "slot5Spell"; + default: + return "slot1Spell"; + } + } + + private static int slotIndexForKey(String key) + { + switch (key) + { + case "slot1Spell": + return 0; + case "slot2Spell": + return 1; + case "slot3Spell": + return 2; + case "slot4Spell": + return 3; + case "slot5Spell": + return 4; + default: + return -1; + } + } + + private void onSlotHotkey(int index) + { + activeSlot = index; + PertTargetSpell spell = slotSpellFor(index); + if (spell != null && config.spell() != spell) + { + configManager.setConfiguration("leftclickcast", "spell", spell); + // MicrobotConfigPanel doesn't refresh individual widgets on ConfigChanged — force a rebuild + // so the top dropdown visibly matches the newly active slot. + eventBus.post(new ExternalPluginsChanged()); + } + if (config.chatFeedback()) + { + String display = spell != null ? spell.getDisplayName() : "(no spell)"; + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .value("Left-Click Cast: now casting " + display) + .build()); + } + } + + private void onEnabledToggleHotkey() + { + boolean newValue = !config.enabled(); + configManager.setConfiguration("leftclickcast", "enabled", newValue); + // MicrobotConfigPanel doesn't subscribe to ConfigChanged for individual checkbox refresh, but it does + // rebuild on ExternalPluginsChanged. Posting that here makes the open config panel re-read this and + // every other config item, so the "Enabled" checkbox visually flips to match the keybind toggle. + eventBus.post(new ExternalPluginsChanged()); + // Chat feedback is emitted by onConfigChanged so checkbox clicks and hotkey presses share one path. + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!"leftclickcast".equals(event.getGroup())) + { + return; + } + String key = event.getKey(); + if ("enabled".equals(key)) + { + if (!config.chatFeedback()) + { + return; + } + boolean enabled = "true".equals(event.getNewValue()); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .value("Left-Click Cast: " + (enabled ? "enabled" : "disabled")) + .build()); + return; + } + if ("spell".equals(key)) + { + // User edited the top dropdown — mirror the value into the currently active slot's config. + // The equality guard stops the ConfigChanged→write→ConfigChanged loop. + PertTargetSpell newSpell = config.spell(); + PertTargetSpell activeSpell = slotSpellFor(activeSlot); + if (newSpell != null && newSpell != activeSpell) + { + configManager.setConfiguration("leftclickcast", slotSpellKeyFor(activeSlot), newSpell); + eventBus.post(new ExternalPluginsChanged()); + } + return; + } + int slot = slotIndexForKey(key); + if (slot == activeSlot && slot >= 0) + { + // User edited the spell for the currently active slot — mirror into the top dropdown. + PertTargetSpell newSpell = slotSpellFor(slot); + if (newSpell != null && newSpell != config.spell()) + { + configManager.setConfiguration("leftclickcast", "spell", newSpell); + eventBus.post(new ExternalPluginsChanged()); + } + } + } + + private void syncTopSpellToActiveSlot() + { + PertTargetSpell active = slotSpellFor(activeSlot); + if (active != null && config.spell() != active) + { + configManager.setConfiguration("leftclickcast", "spell", active); + } + } + + // Fast-path cast: fire two synchronous client.menuAction packets back-to-back so the server processes + // the spell selection and the spell-on-target dispatch on the same game tick. Falls back to + // Rs2Magic.castOn (tab switch + sleeps + clicks) if the spellbook widget isn't loaded yet or the + // spell isn't on the current spellbook. + private void castOnTargetFast(PertTargetSpell spell, Actor target) + { + if (target == null) + { + return; + } + MagicAction magic = spell.getMagicAction(); + Widget magicRoot = client.getWidget(218, 0); + boolean widgetReady = magicRoot != null && magicRoot.getStaticChildren() != null; + if (widgetReady) + { + try + { + int spellWidgetId = magic.getWidgetId(); + // Packet 1: select the spell client-side (WIDGET_TARGET on the spell widget). + client.menuAction(-1, spellWidgetId, MenuAction.WIDGET_TARGET, 1, -1, "Cast", magic.getName()); + // Packet 2: dispatch the selected spell on the target, same tick. + if (target instanceof NPC) + { + NPC npc = (NPC) target; + client.menuAction(0, 0, MenuAction.WIDGET_TARGET_ON_NPC, npc.getIndex(), -1, "Use", npc.getName()); + } + else if (target instanceof Player) + { + Player p = (Player) target; + client.menuAction(0, 0, MenuAction.WIDGET_TARGET_ON_PLAYER, p.getId(), -1, "Use", p.getName()); + } + return; + } + catch (Exception ignored) + { + // Spell not on the active spellbook (e.g., modern while on ancients) — fall through. + } + } + else + { + // Spellbook widget not yet loaded this session; nudge it open so the next click is fast. + Rs2Tab.switchTo(InterfaceTab.MAGIC); + } + // Slow fallback path. Rs2Magic.castOn uses sleepUntil which is a no-op on the client thread, so dispatch async. + final Actor dispatch = target instanceof NPC ? new Rs2NpcModel((NPC) target) : target; + CompletableFuture.runAsync(() -> Rs2Magic.castOn(magic, dispatch)); + } + + // Best-effort: if the user had previously set the legacy `spell` key to a non-default value and + // slot1Spell is still at its default, copy the legacy value into slot1Spell so existing configs keep working. + private void migrateLegacySpellKey() + { + try + { + PertTargetSpell legacy = configManager.getConfiguration( + "leftclickcast", "spell", PertTargetSpell.class); + if (legacy == null || legacy == PertTargetSpell.FIRE_STRIKE) + { + return; + } + if (config.slot1Spell() != PertTargetSpell.FIRE_STRIKE) + { + return; + } + configManager.setConfiguration("leftclickcast", "slot1Spell", legacy); + } + catch (Exception ignored) + { + // Migration is best-effort; ignore any deserialization or storage errors. + } + } + + // A weapon counts as "magic" when its style struct exposes Casting or Defensive Casting. + // Mirrors the core AttackStylesPlugin logic (EnumID.WEAPON_STYLES + ParamID.ATTACK_STYLE_NAME). + private boolean isMagicWeaponEquipped() + { + int weaponType = client.getVarbitValue(VarbitID.COMBAT_WEAPON_CATEGORY); + EnumComposition weaponStyles = client.getEnum(EnumID.WEAPON_STYLES); + if (weaponStyles == null) + { + return false; + } + int styleEnumId = weaponStyles.getIntValue(weaponType); + if (styleEnumId == -1) + { + return false; + } + int[] styleStructs = client.getEnum(styleEnumId).getIntVals(); + for (int structId : styleStructs) + { + StructComposition sc = client.getStructComposition(structId); + if (sc == null) + { + continue; + } + String name = sc.getStringValue(ParamID.ATTACK_STYLE_NAME); + if ("Casting".equalsIgnoreCase(name) || "Defensive Casting".equalsIgnoreCase(name)) + { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java new file mode 100644 index 0000000000..521072613b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java @@ -0,0 +1,96 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import lombok.Getter; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; + +@Getter +public enum PertTargetSpell +{ + // Modern autocastable combat lines (Strike -> Surge) + WIND_STRIKE("Wind Strike", MagicAction.WIND_STRIKE), + WATER_STRIKE("Water Strike", MagicAction.WATER_STRIKE), + EARTH_STRIKE("Earth Strike", MagicAction.EARTH_STRIKE), + FIRE_STRIKE("Fire Strike", MagicAction.FIRE_STRIKE), + WIND_BOLT("Wind Bolt", MagicAction.WIND_BOLT), + WATER_BOLT("Water Bolt", MagicAction.WATER_BOLT), + EARTH_BOLT("Earth Bolt", MagicAction.EARTH_BOLT), + FIRE_BOLT("Fire Bolt", MagicAction.FIRE_BOLT), + WIND_BLAST("Wind Blast", MagicAction.WIND_BLAST), + WATER_BLAST("Water Blast", MagicAction.WATER_BLAST), + EARTH_BLAST("Earth Blast", MagicAction.EARTH_BLAST), + FIRE_BLAST("Fire Blast", MagicAction.FIRE_BLAST), + WIND_WAVE("Wind Wave", MagicAction.WIND_WAVE), + WATER_WAVE("Water Wave", MagicAction.WATER_WAVE), + EARTH_WAVE("Earth Wave", MagicAction.EARTH_WAVE), + FIRE_WAVE("Fire Wave", MagicAction.FIRE_WAVE), + WIND_SURGE("Wind Surge", MagicAction.WIND_SURGE), + WATER_SURGE("Water Surge", MagicAction.WATER_SURGE), + EARTH_SURGE("Earth Surge", MagicAction.EARTH_SURGE), + FIRE_SURGE("Fire Surge", MagicAction.FIRE_SURGE), + + // Ancient autocastable combat lines (Rush/Burst/Blitz/Barrage for each element) + SMOKE_RUSH("Smoke Rush", MagicAction.SMOKE_RUSH), + SHADOW_RUSH("Shadow Rush", MagicAction.SHADOW_RUSH), + BLOOD_RUSH("Blood Rush", MagicAction.BLOOD_RUSH), + ICE_RUSH("Ice Rush", MagicAction.ICE_RUSH), + SMOKE_BURST("Smoke Burst", MagicAction.SMOKE_BURST), + SHADOW_BURST("Shadow Burst", MagicAction.SHADOW_BURST), + BLOOD_BURST("Blood Burst", MagicAction.BLOOD_BURST), + ICE_BURST("Ice Burst", MagicAction.ICE_BURST), + SMOKE_BLITZ("Smoke Blitz", MagicAction.SMOKE_BLITZ), + SHADOW_BLITZ("Shadow Blitz", MagicAction.SHADOW_BLITZ), + BLOOD_BLITZ("Blood Blitz", MagicAction.BLOOD_BLITZ), + ICE_BLITZ("Ice Blitz", MagicAction.ICE_BLITZ), + SMOKE_BARRAGE("Smoke Barrage", MagicAction.SMOKE_BARRAGE), + SHADOW_BARRAGE("Shadow Barrage", MagicAction.SHADOW_BARRAGE), + BLOOD_BARRAGE("Blood Barrage", MagicAction.BLOOD_BARRAGE), + ICE_BARRAGE("Ice Barrage", MagicAction.ICE_BARRAGE), + + // Non-autocastable combat spells + CRUMBLE_UNDEAD("Crumble Undead", MagicAction.CRUMBLE_UNDEAD), + IBAN_BLAST("Iban Blast", MagicAction.IBAN_BLAST), + MAGIC_DART("Magic Dart", MagicAction.MAGIC_DART), + SARADOMIN_STRIKE("Saradomin Strike", MagicAction.SARADOMIN_STRIKE), + CLAWS_OF_GUTHIX("Claws of Guthix", MagicAction.CLAWS_OF_GUTHIX), + FLAMES_OF_ZAMORAK("Flames of Zamorak", MagicAction.FLAMES_OF_ZAMORAK), + + // Arceuus offensive target spells + GHOSTLY_GRASP("Ghostly Grasp", MagicAction.GHOSTLY_GRASP), + SKELETAL_GRASP("Skeletal Grasp", MagicAction.SKELETAL_GRASP), + UNDEAD_GRASP("Undead Grasp", MagicAction.UNDEAD_GRASP), + INFERIOR_DEMONBANE("Inferior Demonbane", MagicAction.INFERIOR_DEMONBANE), + SUPERIOR_DEMONBANE("Superior Demonbane", MagicAction.SUPERIOR_DEMONBANE), + DARK_DEMONBANE("Dark Demonbane", MagicAction.DARK_DEMONBANE), + LESSER_CORRUPTION("Lesser Corruption", MagicAction.LESSER_CORRUPTION), + GREATER_CORRUPTION("Greater Corruption", MagicAction.GREATER_CORRUPTION), + + // Utility target spells + CONFUSE("Confuse", MagicAction.CONFUSE), + WEAKEN("Weaken", MagicAction.WEAKEN), + CURSE("Curse", MagicAction.CURSE), + BIND("Bind", MagicAction.BIND), + SNARE("Snare", MagicAction.SNARE), + ENTANGLE("Entangle", MagicAction.ENTANGLE), + VULNERABILITY("Vulnerability", MagicAction.VULNERABILITY), + ENFEEBLE("Enfeeble", MagicAction.ENFEEBLE), + STUN("Stun", MagicAction.STUN), + TELE_BLOCK("Tele Block", MagicAction.TELE_BLOCK), + TELEOTHER_LUMBRIDGE("Tele Other Lumbridge", MagicAction.TELEOTHER_LUMBRIDGE), + TELEOTHER_FALADOR("Tele Other Falador", MagicAction.TELEOTHER_FALADOR), + TELEOTHER_CAMELOT("Tele Other Camelot", MagicAction.TELEOTHER_CAMELOT); + + private final String displayName; + private final MagicAction magicAction; + + PertTargetSpell(String displayName, MagicAction magicAction) + { + this.displayName = displayName; + this.magicAction = magicAction; + } + + @Override + public String toString() + { + return displayName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java index 8bc8851f0a..29dc4412da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java @@ -25,7 +25,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class AutoLooterPlugin extends Plugin { - public static final String version = "1.1.1"; + public static final String version = "1.1.3"; @Inject DefaultScript defaultScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java index 1678628a9d..79bd84eb48 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java @@ -50,6 +50,10 @@ public boolean run(AutoLooterConfig config) { switch (state) { case LOOTING: + // Walker may still be carrying us back (post-bank or post-stray). + // Scanning from the wrong tile would trip the world-hop counter. + if (isAwayFromBase(config)) return; + if (config.worldHop()) { if (config.looterStyle() == DefaultLooterStyle.ITEM_LIST) { lootExists = Arrays.stream(config.listOfItemsToLoot().trim().split(",")) @@ -129,7 +133,12 @@ public boolean run(AutoLooterConfig config) { break; case BANKING: + // Stay in BANKING until the bank trip is fully complete: items deposited + // AND we're back at the loot spot. Flipping to LOOTING mid-walk-back + // would let the loot scan run from the bank tile. if (Rs2Inventory.emptySlotCount() <= config.minFreeSlots()) return; + if (isAwayFromBase(config)) return; + failedLootAttempts = 0; state = LooterState.LOOTING; break; } @@ -184,6 +193,12 @@ public boolean handleWalk(AutoLooterConfig config) { return true; } + private boolean isAwayFromBase(AutoLooterConfig config) { + return initialPlayerLocation == null + || Rs2Player.isMoving() + || Rs2Player.getWorldLocation().distanceTo(initialPlayerLocation) > config.distanceToStray(); + } + private void applyAntiBanSettings() { Rs2AntibanSettings.antibanEnabled = true; Rs2AntibanSettings.usePlayStyle = true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java index d10f4cebc5..bfc8a7a14c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.looter.scripts; -import net.runelite.api.GameObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.looter.AutoLooterConfig; @@ -9,7 +8,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -54,9 +53,9 @@ public boolean run(AutoLooterConfig config) { } if (config.worldHop() && Rs2Player.hopIfPlayerDetected(1, 10, 10)) return; - GameObject flaxObject = Rs2GameObject.findObject("flax", false, config.distanceToStray(), true, initialPlayerLocation); + Rs2TileObjectModel flaxObject = Microbot.getRs2TileObjectCache().query().withName("flax").within(initialPlayerLocation, config.distanceToStray()).nearestOnClientThread(); if (flaxObject != null) { - if(Rs2GameObject.interact(flaxObject, "pick")){ + if(flaxObject.click("pick")){ Rs2Antiban.actionCooldown(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java index f482b23d11..8249990006 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.looter.scripts; -import net.runelite.api.GameObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.looter.AutoLooterConfig; @@ -8,13 +7,11 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import java.util.Comparator; -import java.util.Optional; import java.util.concurrent.TimeUnit; public class NatureRuneChestScript extends Script { @@ -50,12 +47,9 @@ public boolean run(AutoLooterConfig config) { Rs2Player.logoutIfPlayerDetected(1, 10); return; } - Optional natureRuneChest = Rs2GameObject.getGameObjects().stream() - .filter(obj -> obj.getId() == config.natureRuneChestLocation().getObjectID()) - .sorted(Comparator.comparingInt(obj -> Rs2Player.getWorldLocation().distanceTo(obj.getWorldLocation()))) - .findFirst(); - if (natureRuneChest.isPresent()) { - if(Rs2GameObject.interact(natureRuneChest.get(), "Search for traps")){ + Rs2TileObjectModel natureRuneChest = Microbot.getRs2TileObjectCache().query().withId(config.natureRuneChestLocation().getObjectID()).nearest(); + if (natureRuneChest != null) { + if(natureRuneChest.click("Search for traps")){ Rs2Antiban.actionCooldown(); sleepUntilTrue(() -> !Rs2Player.isInteracting(), 500, 8000); sleep(Rs2Random.between(18000, 20000)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java index 4463542cfd..1c703db01b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class LunarTabletsPlugin extends Plugin { - public static final String version = "2.0.1"; + public static final String version = "2.0.2"; @Inject private LunarTabletsConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java index fbfb15a0f9..ad193326b2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java @@ -7,7 +7,6 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -136,7 +135,8 @@ public void makeTablets(){ } } else { // interact with lecturn - if(Rs2GameObject.interact("Lectern", "Study")){ + var lectern = Microbot.getRs2TileObjectCache().query().withName("Lectern").nearestOnClientThread(); + if(lectern != null && lectern.click("Study")){ sleep(generateRandomNumber(0,1000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java index f37affa40b..e03170c3ca 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class MageTrainingArenaPlugin extends Plugin { - public static final String version = "1.1.5"; + public static final String version = "1.1.8"; @Inject private MageTrainingArenaConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index a03cf10e6c..b0b0bbeddb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -14,16 +14,16 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.*; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -189,7 +189,7 @@ public boolean run(MageTrainingArenaConfig config) { || currentPoints.get(currentRoom.getPoints()) >= getRequiredPoints().get(currentRoom.getPoints()) * (config.buyRewards() ? 1 : (buyable + 1))) { // Deposit items before leaving to maximize points if (currentRoom == Rooms.ENCHANTMENT && Rs2Inventory.contains(ItemID.MAGICTRAINING_ENCHAN_SHAPEORB)) { - Rs2GameObject.interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); Rs2Player.waitForWalking(); } if (currentRoom == Rooms.GRAVEYARD && Rs2Inventory.contains(ItemID.BANANA, ItemID.PEACH)) { @@ -216,7 +216,8 @@ public boolean run(MageTrainingArenaConfig config) { sleepGaussian(600, 150); } catch (Exception ex) { - if (ex instanceof InterruptedException) + if (ex instanceof InterruptedException + || ex.getCause() instanceof InterruptedException) return; System.out.println(ex.getMessage()); @@ -331,19 +332,19 @@ private void handleEnchantmentRoom() { if (!Rs2Walker.walkTo(new WorldPoint(3363, 9640, 0))) return; - Rs2GameObject.interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); Rs2Player.waitForWalking(); return; } - boolean successFullLoot = Rs2GroundItem.loot(ItemID.MAGICTRAINING_DRAGONSTONE, 12) && Rs2Inventory.waitForInventoryChanges(5000); + boolean successFullLoot = Microbot.getRs2TileItemCache().query().withId(ItemID.MAGICTRAINING_DRAGONSTONE).interact("Take") && Rs2Inventory.waitForInventoryChanges(5000); if (successFullLoot && Rs2Inventory.emptySlotCount() > 0) return; var bonusShape = getBonusShape(); if (bonusShape == null) return; - var object = Rs2GameObject.getGameObject(obj -> (obj.getId() == bonusShape.getObjectId()) && Rs2Camera.isTileOnScreen(obj)); + Rs2TileObjectModel object = Microbot.getRs2TileObjectCache().query().withId(bonusShape.getObjectId()).where(obj -> Rs2Camera.isTileOnScreen(obj.getLocalLocation())).nearest(); if (object == null) { var index = Rs2Random.between(0, 4); Rs2Walker.walkTo(new WorldPoint[]{ @@ -369,7 +370,7 @@ private void handleEnchantmentRoom() { Rs2Inventory.interact(itemId); sleepUntil(() -> !Rs2Inventory.contains(itemId) || itemId != ItemID.MAGICTRAINING_DRAGONSTONE && bonusShape != getBonusShape(), 20000); - } else if (Rs2GameObject.interact(object, "Take-from")) { + } else if (object.click("Take-from")) { Rs2Inventory.waitForInventoryChanges(1000); } else if (Rs2Player.getWorldLocation().distanceTo(object.getWorldLocation()) > 10){ Rs2Walker.walkFastCanvas(object.getWorldLocation()); @@ -415,8 +416,8 @@ private void handleTelekineticRoom() { sleep(400, 600); } - var localTarget = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); - var targetConverted = WorldPoint.fromLocalInstance(Microbot.getClient(), Objects.requireNonNull(localTarget)); + var localTarget = Microbot.getClientThread().invoke(() -> LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target)); + var targetConverted = Microbot.getClientThread().invoke(() -> WorldPoint.fromLocalInstance(Microbot.getClient(), Objects.requireNonNull(localTarget))); if (Rs2Camera.getZoom() < 40 || Rs2Camera.getZoom() > 60) { Rs2Camera.setZoom(Rs2Random.betweenInclusive(40,60)); @@ -427,12 +428,11 @@ private void handleTelekineticRoom() { if (guardian.getWorldLocation().equals(room.getFinishLocation())) { sleepUntil(() -> room.getGuardian().getId() == NpcID.MAGICTRAINING_GUARD_MAZE_COMPLETE); sleep(200, 400); - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian()), "New-maze"); + new Rs2NpcModel(room.getGuardian()).click("New-maze"); sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(teleRoom.getArea()) != 0); } else { while (!Rs2Player.getWorldLocation().equals(targetConverted) - && (Microbot.getClient().getLocalDestinationLocation() == null - || !Microbot.getClient().getLocalDestinationLocation().equals(localTarget))) { + && !Objects.equals(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalDestinationLocation()), localTarget)) { if (Rs2Camera.isTileOnScreen(localTarget)) { Rs2Walker.walkFastCanvas(targetConverted); sleepGaussian(600, 150); @@ -442,11 +442,16 @@ private void handleTelekineticRoom() { sleepUntil(() -> !Rs2Player.isMoving()); } + boolean noTelegrabProjectile = Microbot.getClientThread() + .runOnClientThreadOptional(() -> StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false) + .noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL)) + .orElse(true); + var position = Microbot.getClientThread().invoke(room::getPosition); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() - && StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false).noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL) + && noTelegrabProjectile && !TelekineticRoom.getMoves().isEmpty() - && TelekineticRoom.getMoves().peek() == room.getPosition() + && TelekineticRoom.getMoves().peek() == position && room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING && !room.getGuardian().getLocalLocation().equals(room.getDestination())) { Rs2Magic.cast(MagicAction.TELEKINETIC_GRAB); @@ -454,7 +459,7 @@ private void handleTelekineticRoom() { if (Rs2Random.dicePercentage(50)) { Rs2Camera.turnTo(room.getGuardian()); } - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian())); + new Rs2NpcModel(room.getGuardian()).click(); sleepUntil(()->room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING); } } @@ -544,13 +549,13 @@ private void handleAlchemistRoom() { } if (room.getSuggestion() == null) { - Rs2GameObject.interact("Cupboard", "Search"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Cupboard").interact("Search")); sleep(300,600); if (sleepUntilTrue(Rs2Player::isMoving, 100, 1000)) sleepUntil(() -> !Rs2Player.isMoving()); } else { - Rs2GameObject.interact(room.getSuggestion().getGameObject(), "Take-5"); + new Rs2TileObjectModel(room.getSuggestion().getGameObject()).click("Take-5"); Rs2Inventory.waitForInventoryChanges(3000); sleep(300,600); } @@ -572,7 +577,7 @@ private void buyReward(Rewards reward) { return; if (!Rs2Widget.isWidgetVisible(197, 0)) { - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_REWARDS, "Trade-with"); + Microbot.getRs2NpcCache().query().withId(NpcID.MAGICTRAINING_GUARD_REWARDS).interact("Trade-with"); sleepUntil(() -> Rs2Widget.isWidgetVisible(197, 0)); sleepGaussian(600, 150); return; @@ -614,7 +619,7 @@ public static void enterRoom(Rooms room) { if (!Rs2Walker.walkTo(portalPoint)) return; - Rs2GameObject.interact(room.getTeleporter(), "Enter"); + Microbot.getRs2TileObjectCache().query().interact(room.getTeleporter(), "Enter"); Rs2Player.waitForAnimation(); if (Rs2Widget.hasWidget("You must talk to the Entrance Guardian")) firstTime = true; @@ -641,7 +646,7 @@ public static void leaveRoom() { if (!Rs2Walker.walkTo(exit)) return; - Rs2GameObject.interact(ObjectID.MAGICTRAINING_RETURNDOOR, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_RETURNDOOR, "Enter"); Rs2Player.waitForWalking(); } @@ -651,7 +656,7 @@ private boolean handleFirstTime() { return true; if (!Rs2Dialogue.isInDialogue()) - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_ENTRANCE, "Talk-to"); + Microbot.getRs2NpcCache().query().withId(NpcID.MAGICTRAINING_GUARD_ENTRANCE).interact("Talk-to"); else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("I'm new to this place")) Rs2Widget.clickWidget("I'm new to this place"); else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("Thanks, bye!")) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java index 4ae40e66a3..1d87561aef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java @@ -3,7 +3,7 @@ import lombok.Setter; import net.runelite.api.GameObject; import net.runelite.api.Player; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.*; import net.runelite.client.ui.overlay.components.LineComponent; @@ -144,6 +144,8 @@ private void addLine(final String left) private void addObjectLine(final GameObject left) { - panelComponent.getChildren().add(LineComponent.builder().left(Rs2GameObject.convertGameObjectToObjectComposition(left).getName()).right(": "+Objects.requireNonNull(Hotspot.getByObjectId(left.getId())).getRequiredAction()).build()); + var obj = Microbot.getRs2TileObjectCache().query().withId(left.getId()).nearest(); + if (obj == null) return; + panelComponent.getChildren().add(LineComponent.builder().left(obj.getObjectComposition().getName()).right(": "+Objects.requireNonNull(Hotspot.getByObjectId(left.getId())).getRequiredAction()).build()); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java index 3c34490d64..fc4d12fe4d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java @@ -55,7 +55,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class MahoganyHomesPlugin extends Plugin { - public static final String version = "0.0.9"; + public static final String version = "0.0.11"; private static final List PLANKS = Arrays.asList(ItemID.PLANK, ItemID.OAK_PLANK, ItemID.TEAK_PLANK, ItemID.MAHOGANY_PLANK); private static final List PLANK_NAMES = Arrays.asList("Plank", "Oak plank", "Teak plank", "Mahogany plank"); private static final Map MAHOGANY_HOMES_REPAIRS = new HashMap<>(); @@ -250,7 +250,7 @@ public void startUp() { overlayManager.add(textOverlay); overlayManager.add(highlightOverlay); overlayManager.add(plankSackOverlay); - clientThread.invoke(() -> { + clientThread.invokeLater(() -> { if (client.getGameState() == GameState.LOGGED_IN) { loadFromConfig(); updateVarbMap(); @@ -317,7 +317,7 @@ public void onGameStateChanged(GameStateChanged e) { @Subscribe public void onUsernameChanged(UsernameChanged e) { - clientThread.invoke(this::loadFromConfig); + clientThread.invokeLater(this::loadFromConfig); } @Subscribe @@ -337,9 +337,11 @@ public void onOverlayMenuClicked(OverlayMenuClicked e) { } if (e.getEntry().getOption().equals(MahoganyHomesOverlay.CLEAR_OPTION)) { - setCurrentHome(null); - updateConfig(); - lastChanged = null; + clientThread.invokeLater(() -> { + applyCurrentHome(null); + updateConfig(); + lastChanged = null; + }); } @@ -455,8 +457,10 @@ public void onChatMessage(ChatMessage e) { if (CONTRACT_FINISHED.matcher(Text.removeTags(e.getMessage())).matches()) { sessionContracts++; sessionPoints += getPointsForCompletingTask(); - setCurrentHome(null); - updateConfig(); + clientThread.invokeLater(() -> { + applyCurrentHome(null); + updateConfig(); + }); } } @@ -649,8 +653,11 @@ private void checkForAssignmentDialog() { for (final Home h : Home.values()) { if (h.getName().equalsIgnoreCase(name) && (currentHome != h || isPluginTimedOut())) { - setCurrentHome(h); - updateConfig(); + final Home selected = h; + clientThread.invokeLater(() -> { + applyCurrentHome(selected); + updateConfig(); + }); break; } } @@ -658,28 +665,30 @@ private void checkForAssignmentDialog() { } public void setCurrentHome(final Home h) { - clientThread.invoke(() -> { - currentHome = h; - client.clearHintArrow(); - lastChanged = Instant.now(); - lastCompletedCount = 0; - varbMap.clear(); - - if (currentHome == null) { - worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); - contractTier = 0; - return; - } + clientThread.invokeLater(() -> applyCurrentHome(h)); + } - if (config.worldMapIcon()) { - worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); - worldMapPointManager.add(new MahoganyHomesWorldPoint(h.getLocation(), this)); - } + private void applyCurrentHome(final Home h) { + currentHome = h; + client.clearHintArrow(); + lastChanged = Instant.now(); + lastCompletedCount = 0; + varbMap.clear(); - if (config.displayHintArrows() && client.getLocalPlayer() != null) { - refreshHintArrow(client.getLocalPlayer().getWorldLocation()); - } - }); + if (currentHome == null) { + worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); + contractTier = 0; + return; + } + + if (config.worldMapIcon()) { + worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); + worldMapPointManager.add(new MahoganyHomesWorldPoint(h.getLocation(), this)); + } + + if (config.displayHintArrows() && client.getLocalPlayer() != null) { + refreshHintArrow(client.getLocalPlayer().getWorldLocation()); + } } @@ -711,10 +720,10 @@ private void loadFromConfig() { try { final Home h = Home.valueOf(name.trim().toUpperCase()); - setCurrentHome(h); + applyCurrentHome(h); } catch (IllegalArgumentException e) { log.warn("Stored unrecognized home: {}", name); - currentHome = null; + applyCurrentHome(null); configManager.setConfiguration(group, MahoganyHomesConfig.HOME_KEY, null); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java index b79511fa82..d3ceb90412 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java @@ -13,13 +13,11 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -27,6 +25,7 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.util.*; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -191,7 +190,7 @@ private void fix() { private void interactWithObject(GameObject object) { Hotspot hotspot = Hotspot.getByObjectId(object.getId()); String action = Objects.requireNonNull(hotspot).getRequiredAction(); - if (Rs2GameObject.interact(object, action)) { + if (Microbot.getRs2TileObjectCache().query().withId(object.getId()).interact(action)) { sleepUntil(() -> { String newAction = Objects.requireNonNull(Hotspot.getByObjectId(object.getId())).getRequiredAction(); return !newAction.equals(action); @@ -217,7 +216,9 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation if (door == null) continue; - var objectComp = Rs2GameObject.getObjectComposition(door.getId()); + var doorModel = Microbot.getRs2TileObjectCache().query().withId(door.getId()).nearest(); + if (doorModel == null) continue; + var objectComp = doorModel.getObjectComposition(); if (objectComp == null) continue; String name = objectComp.getName(); @@ -229,7 +230,10 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation } List doorNames = doors.stream() - .map(d -> Rs2GameObject.getObjectComposition(d.getId()).getName()) + .map(d -> { + var m = Microbot.getRs2TileObjectCache().query().withId(d.getId()).nearest(); + return m != null ? m.getObjectComposition().getName() : "unknown"; + }) .collect(Collectors.toList()); System.out.println("Doors found: " + doorNames + " Size: " + doors.size()); @@ -238,7 +242,8 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation // log("Doors found: %s", doors.size()); for (TileObject door : doors) { - ObjectComposition doorComp = Rs2GameObject.getObjectComposition(door.getId()); + var doorObj = Microbot.getRs2TileObjectCache().query().withId(door.getId()).nearest(); + ObjectComposition doorComp = doorObj != null ? doorObj.getObjectComposition() : null; List actions = null; if (doorComp != null) { actions = Arrays.asList(doorComp.getActions()); @@ -247,7 +252,7 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation log("Opening door at: %s", door.getWorldLocation()); logInfo("Opening door at: {}", door.getWorldLocation()); - if (Rs2GameObject.interact(door, "Open")) { + if (Microbot.getRs2TileObjectCache().query().withId(door.getId()).interact("Open")) { Rs2Player.waitForWalking(); sleep(200, 500); // if it's the last door in the list return true @@ -262,8 +267,8 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation private void tryToUseLadder() { log("Walker missing transport, trying to find ladder manually."); int plane = Rs2Player.getWorldLocation().getPlane(); - TileObject closestLadder = Rs2GameObject.findObject(plugin.getCurrentHome().getLadders()); - if (Rs2GameObject.interact(closestLadder)) { + var closestLadder = Microbot.getRs2TileObjectCache().query().withIds(Arrays.stream(plugin.getCurrentHome().getLadders()).mapToInt(Integer::intValue).toArray()).nearest(); + if (closestLadder != null && closestLadder.click()) { sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() != plane, 5000); sleep(200, 600); } @@ -284,35 +289,31 @@ private void finish() { } } } - var npc = Rs2Npc.getNpc(plugin.getCurrentHome().getNpcId()); + var npc = Microbot.getRs2NpcCache().query().withId(plugin.getCurrentHome().getNpcId()).nearest(); if (npc == null && Rs2Player.getWorldLocation().getPlane() > 0) { log("We are on the wrong floor, Trying to find ladder to go down"); int playerPlane = Rs2Player.getWorldLocation().getPlane(); - List ladders = Rs2GameObject.getGameObjects( - obj -> Arrays.stream(plugin.getCurrentHome().getLadders()) - .anyMatch(id -> id == obj.getId()) - && obj.getWorldLocation().getPlane() == playerPlane - ); - GameObject closestLadder = ladders.stream() + var ladders = Microbot.getRs2TileObjectCache().query() + .withIds(Arrays.stream(plugin.getCurrentHome().getLadders()).mapToInt(Integer::intValue).toArray()) + .where(obj -> obj.getWorldLocation().getPlane() == playerPlane) + .toList(); + var closestLadder2 = ladders.stream() .min(Comparator.comparingInt(obj -> obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) .orElse(null); - Rs2WorldPoint objectLocation = Rs2Tile.getNearestWalkableTile(closestLadder); - if (!openDoorToObject(closestLadder, objectLocation)) { - if (Rs2GameObject.interact(closestLadder)) { + if (closestLadder2 != null && closestLadder2.click()) { sleepUntil( () -> Rs2Player.getWorldLocation().getPlane() == 0 , 5000); return; - } } } if (npc != null) { Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); log("Local NPC path distance: " + npcLocation.distanceToPath(Rs2Player.getWorldLocation())); if (npcLocation.distanceToPath(Rs2Player.getWorldLocation()) < 20) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log("Getting reward from NPC"); sleepUntil(Rs2Dialogue::hasContinue, 10000); if (Rs2Dialogue.hasDialogueText("Please excuse me, I'm rather busy.")) { @@ -350,14 +351,7 @@ private void getNewContract() { // Search for Mahogany Homes contract NPCs directly by name - var npc = Rs2Npc.getNpcs() - .filter(n -> n.getName() != null && - (n.getName().equals("Amy") || - n.getName().equals("Marlo") || - n.getName().equals("Ellie") || - n.getName().equals("Angelo"))) - .findFirst() - .orElse(null); + var npc = Microbot.getRs2NpcCache().query().withNames("Amy", "Marlo", "Ellie", "Angelo").nearestOnClientThread(); if (npc == null) { log("No contract NPC found, waiting before retry"); @@ -365,7 +359,7 @@ private void getNewContract() { return; } log("NPC found: " + npc.getName()); - if (Rs2Npc.interact(npc, "Contract")) { + if (npc.click("Contract")) { handleContractDialogue(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java index fe70bab933..74ad3af6fc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java @@ -38,7 +38,7 @@ @Slf4j public class TheMessPlugin extends Plugin { - static final String version = "1.0.2"; + static final String version = "1.1.0"; @Inject private TheMessConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java index 4962120886..c374705532 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java @@ -1,10 +1,11 @@ package net.runelite.client.plugins.microbot.mess; import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.GameState; import net.runelite.api.Skill; +import net.runelite.api.TileObject; +import net.runelite.api.VarClientStr; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; @@ -26,753 +27,640 @@ import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; -import net.runelite.client.plugins.microbot.util.settings.Rs2Settings; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import org.slf4j.event.Level; import java.awt.event.KeyEvent; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.function.BooleanSupplier; import java.util.stream.Collectors; -import java.util.stream.IntStream; @Slf4j public class TheMessScript extends Script { private static final WorldPoint UTENSIL_CUPBOARD_LOC = new WorldPoint(1644, 3624, 0); - private static final WorldPoint FOOD_CUPBOARD_LOC = new WorldPoint(1645, 3623, 0); - private static final WorldPoint SINK_LOC = new WorldPoint(1644, 3628, 0); - private static final WorldPoint MEAT_TABLE_LOC = new WorldPoint(1645, 3630, 0); - private static final WorldPoint CLAY_OVEN_LOC = new WorldPoint(1648, 3627, 0); - private static final WorldPoint BUFFET_TABLE_LOC = new WorldPoint(1640, 3629, 0); - private static final int STEW_WIDGET_BAR_ID = 15400974; - private static final int PIE_WIDGET_BAR_ID = 15400966; - private static final int PIZZA_WIDGET_BAR_ID = 15400970; - private TheMessOverlay overlay; - private TheMessConfig config; + private static final WorldPoint FOOD_CUPBOARD_LOC = new WorldPoint(1645, 3623, 0); + private static final WorldPoint SINK_LOC = new WorldPoint(1644, 3628, 0); + private static final WorldPoint MEAT_TABLE_LOC = new WorldPoint(1645, 3630, 0); + private static final WorldPoint CLAY_OVEN_LOC = new WorldPoint(1648, 3627, 0); + private static final WorldPoint BUFFET_TABLE_LOC = new WorldPoint(1640, 3629, 0); + private static final WorldPoint MESS_HUB = new WorldPoint(1645, 3627, 0); + + private static final int SHOP_WIDGET_ID = 15859715; + private static final int SHOP_CLOSE_PARENT = 15859713; + + private static final int APPRECIATION_BAR_PIE = 15400966; + private static final int APPRECIATION_BAR_PIZZA = 15400970; + private static final int APPRECIATION_BAR_STEW = 15400974; - @Getter - @Setter - private State currentState = State.WAITING; + private static final int BATCH_SIZE = 14; + private static final int PIZZA_BATCH_SIZE = 13; // 2 knife slots reserved + + private TheMessConfig config; + private TheMessOverlay overlay; public boolean run(TheMessConfig config, TheMessOverlay overlay) { - debug("The Mess Script is starting up..."); - Microbot.enableAutoRunOn = false; - this.overlay = overlay; this.config = config; - - setCurrentState(State.WAITING); - setOrderOfStates(); + this.overlay = overlay; + Microbot.enableAutoRunOn = false; Rs2Antiban.setActivity(Activity.GENERAL_COOKING); Rs2AntibanSettings.naturalMouse = true; - Rs2AntibanSettings.actionCooldownActive = true; - Rs2Antiban.setTIMEOUT(Rs2Random.betweenInclusive(1, 4)); - /* - * Set camera settings for the script. - * To avoid clicking through UI elements like inventory and such. - */ Rs2Camera.setZoom(Rs2Random.randomGaussian(200, 20)); - Rs2Camera.setYaw((Rs2Random.dicePercentage(50)? Rs2Random.randomGaussian(750, 50) : Rs2Random.randomGaussian(1700, 50))); + Rs2Camera.setYaw(Rs2Random.dicePercentage(50) + ? Rs2Random.randomGaussian(750, 50) + : Rs2Random.randomGaussian(1700, 50)); Rs2Camera.setPitch(Rs2Random.betweenInclusive(418, 512)); - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; - if (BreakHandlerScript.isBreakActive() && getCurrentState() == State.WAITING) return; + setStatus("Starting"); + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(this::tick, + 0, 600, TimeUnit.MILLISECONDS); + return true; + } + + private long lastDecisionLogMs = 0; + private String lastDecision = ""; - handleState(); + private void tick() { + try { + if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; + if (BreakHandlerScript.isBreakActive()) { setStatus("Break"); return; } - } catch (Exception ex) { - System.out.println(ex.getMessage()); + if (!inMess()) { logDecision("walkToMess"); walkToMess(); return; } + if (hasJunk()) { logDecision("cleanInventory"); runCleanInventory(); return; } + if (violatesBatchInvariant()) { setStatus("FAIL: batch invariant"); shutdown(); return; } + dropBurnt(); + + switch (config.dish()) { + case STEW: stewTick(); break; + case MEAT_PIE: meatPieTick(); break; + case PIZZA: pizzaTick(); break; } - }, 0, 600, TimeUnit.MILLISECONDS); - return true; + } catch (Exception ex) { + log.warn("tick failed: {}", ex.getMessage(), ex); + } } - private void handleState() { - switch (getCurrentState()) { - case WAITING: - if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; - if (BreakHandlerScript.isBreakActive()) return; - break; - case GET_EMPTY_BOWLS: - case GET_KNIFE: - case GET_EMPTY_PIE_DISHES: - sleepUntil(getUtensils()); - break; - case RETURN_EMPTY_BOWLS: - sleepUntil(returnEmptyBowls()); - break; - case USE_SINK: - sleepUntil(fillBowl()); - break; - case GET_MEAT: - sleepUntil(getMeat()); - break; - case USE_CLAY_OVEN: - case FINISH_COOKING: - sleepUntil(cook()); - break; - case COMBINE_MEAT_PIE: - case COMBINE_MEAT_WATER: - case COMBINE_STEW: - case COMBINE_PASTRY_DOUGH: - case COMBINE_PIE_SHELL: - case COMBINE_PIZZA_BASE: - case COMBINE_PIZZA_TOMATO: - case COMBINE_PIZZA_CHEESE: - case COMBINE_PIZZA_PINEAPPLE: - case CUT_PINEAPPLE: - sleepUntil(combineItems(), 60000); - break; - case GET_CHEESE: - case GET_TOMATOES: - case GET_PINEAPPLES: - case GET_FLOUR: - case GET_POTATOES: - sleepUntil(getFood()); - break; - case USE_BUFFET_TABLE: - if (isUnderAppreciationThreshold()) { - setCurrentState(State.HOP_WORLD); - return; - } - if (Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { - Rs2GameObject.interact("Buffet table", "Serve"); - sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.COOKING), 10000); - } else { - debug("Cannot reach the buffet table, waiting..."); - } - break; - case GETTING_READY: - if (!inTheMess()) return; - sleepUntil(cleanInventory()); - break; - case HOP_WORLD: - if (!hopWorlds()) return; - break; - default: - break; - } - if (currentState.getNext() != null) { - setCurrentState(currentState.getNext()); - overlay.setStatus(currentState.getStatus()); + private void logDecision(String decision) { + long now = System.currentTimeMillis(); + // Only log when the decision changes, or every 10s on the same decision (so a stuck loop is visible). + if (!decision.equals(lastDecision) || now - lastDecisionLogMs > 10_000) { + log.info("[mess] step={} pos={} invCount={} dish={}", + decision, Rs2Player.getWorldLocation(), Rs2Inventory.count(), config.dish()); + lastDecision = decision; + lastDecisionLogMs = now; } } - private BooleanSupplier returnEmptyBowls() { - return () -> { - if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, Rs2GameObject.getGameObject(UTENSIL_CUPBOARD_LOC).getId()); - sleepUntil(() -> Rs2Inventory.count(ItemID.BOWL_EMPTY) == 0, 10000); - Rs2Antiban.actionCooldown(); - return !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY); - } - return true; - }; + // ---------- per-dish flows ---------- + + private void stewTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so leftovers from burns carry into the next batch. + if (has(ItemID.HOSIDIUS_SERVERY_STEW)) { serveAndMaybeHop(APPRECIATION_BAR_STEW); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW); return; } + if (has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); return; } + if (has(ItemID.HOSIDIUS_SERVERY_MEATWATER) && has(ItemID.HOSIDIUS_SERVERY_POTATO)){ combineAll(ItemID.HOSIDIUS_SERVERY_MEATWATER, ItemID.HOSIDIUS_SERVERY_POTATO, "Combining stew"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_MEATWATER)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POTATO, count(ItemID.HOSIDIUS_SERVERY_MEATWATER)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_COOKED_MEAT) && has(ItemID.BOWL_WATER)) { combineAll(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, "Combining meat + water"); return; } + // Finish filling bowls before sizing the raw-meat withdrawal — otherwise a partial bowl_water + // count (e.g., fillBowls timed out at 12/14) would drive takeRawMeat to under-fetch. + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + // Only advance to the raw-meat phase with a full batch of bowl_water. With fewer (e.g., burns + // left some unused last round), fall through to the bowl chain to top up to BATCH_SIZE rather + // than running a tiny 2-stew loop with all its withdraw/cook/combine round-trips. + if (count(ItemID.BOWL_WATER) >= BATCH_SIZE && !has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { takeRawMeat(BATCH_SIZE); return; } + + // Fresh batch — top up to BATCH_SIZE accounting for any leftover items in the chain. + int leftover = count(ItemID.BOWL_WATER) + count(ItemID.HOSIDIUS_SERVERY_MEATWATER) + count(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW); + int needed = Math.max(1, BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); } - private boolean hopWorlds() { - info("Hopping worlds..."); - int currentWorld = Microbot.getClient().getWorld(); - Microbot.hopToWorld(Login.getRandomWorld(true)); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); - if (Microbot.getClient().getWorld() != currentWorld) { - info("Successfully hopped to a new world."); - return true; - } else { - debug("Failed to hop worlds, retrying..."); - } - return false; + private void meatPieTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so burn-induced leftovers (extra shells) + // carry into the next loop and the chain tops up rather than over-fetching. + if (has(ItemID.HOSIDIUS_SERVERY_MEAT_PIE)) { serveAndMaybeHop(APPRECIATION_BAR_PIE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) && has(ItemID.HOSIDIUS_SERVERY_COOKED_MEAT)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIE_SHELL, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, "Filling pies"); return; } + // Return bowls only when shells are at full batch (post-combine state of piedish+dough). + // Before that, any BOWL_EMPTY in inventory is feedstock for the dough chain — returning + // it would infinite-loop (withdraw → return → withdraw...). + if (count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) >= BATCH_SIZE && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + // Only proceed to raw-meat phase when we have a full batch of shells. With fewer shells + // (e.g., burns left some unused), fall through to the dough chain to top up to BATCH_SIZE. + if (count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) >= BATCH_SIZE) { takeRawMeat(BATCH_SIZE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIEDISH) && has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIEDISH, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, "Forming shells"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) && !has(ItemID.HOSIDIUS_SERVERY_PIEDISH)) { withdrawFromUtensil(ItemID.HOSIDIUS_SERVERY_PIEDISH, count(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH)); return; } + if (has(ItemID.BOWL_WATER) && has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { combineDoughDialog(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_POT_FLOUR, "Pastry dough"); return; } + if (has(ItemID.BOWL_EMPTY) && !has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POT_FLOUR, count(ItemID.BOWL_EMPTY)); return; } + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + + // Fresh batch — top up to BATCH_SIZE accounting for any leftover items in the chain. + int leftover = count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) + + count(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) + + count(ItemID.BOWL_WATER); + int needed = Math.max(1, BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); } - private boolean isUnderAppreciationThreshold() { - Widget appreciationBarWidget; - switch (config.dish()) { - case MEAT_PIE: - appreciationBarWidget = Rs2Widget.getWidget(PIE_WIDGET_BAR_ID); - break; - case STEW: - appreciationBarWidget = Rs2Widget.getWidget(STEW_WIDGET_BAR_ID); - break; - case PIZZA: - appreciationBarWidget = Rs2Widget.getWidget(PIZZA_WIDGET_BAR_ID); - break; - default: - debug("Unknown dish selected, cannot check appreciation."); - return false; - } - - Widget filledBar = appreciationBarWidget.getChild(0); + private void pizzaTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so leftovers from burns carry into the next batch. + if (has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA)) { serveAndMaybeHop(APPRECIATION_BAR_PIZZA); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA) && has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS)) { combineAll(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, "Adding pineapple"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE) && has(ItemID.KNIFE)) { combineAll(ItemID.KNIFE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE, "Cutting pineapple"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) && has(ItemID.HOSIDIUS_SERVERY_CHEESE)) { combineAll(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, ItemID.HOSIDIUS_SERVERY_CHEESE, "Adding cheese"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) && !has(ItemID.HOSIDIUS_SERVERY_CHEESE)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_CHEESE, count(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && has(ItemID.HOSIDIUS_SERVERY_TOMATO)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_TOMATO, "Adding tomato"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && !has(ItemID.HOSIDIUS_SERVERY_TOMATO)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_TOMATO, count(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA) && !has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS) && !has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_PINEAPPLE, count(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + if (has(ItemID.BOWL_WATER) && has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { combineDoughDialog(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_POT_FLOUR, "Pizza base"); return; } + if (has(ItemID.BOWL_EMPTY) && !has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POT_FLOUR, count(ItemID.BOWL_EMPTY)); return; } + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + if (count(ItemID.KNIFE) < 2) { withdrawFromUtensil(ItemID.KNIFE, 2); return; } + + // Fresh batch — top up to PIZZA_BATCH_SIZE accounting for any leftovers in the chain. + int leftover = count(ItemID.BOWL_WATER) + + count(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) + + count(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) + + count(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA) + + count(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA); + int needed = Math.max(1, PIZZA_BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); + } - if (filledBar == null) { - debug("Appreciation bar widget not found, cannot check appreciation."); - return false; - } + // ---------- step handlers ---------- - int filledPercentage = filledBar.getWidth() * 100 / appreciationBarWidget.getWidth(); + private void walkToMess() { + setStatus("Walking to Mess"); + Rs2Walker.walkTo(MESS_HUB, 4); + } - if (filledPercentage < config.appreciation_threshold()) { - debug("Appreciation is below threshold."); - return true; - } else { - debug("Appreciation is above threshold."); - } - return false; + private boolean inMess() { + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.getWidget(InterfaceID.HosidiusServeryHud.CONTENT) != null + ).orElse(false); } - private boolean inTheMess() { - if (Rs2Widget.getWidget(InterfaceID.HosidiusServeryHud.CONTENT) != null) { - debug("Already in the area, no need to move location."); - return true; + private boolean withdrawFromUtensil(int itemId, int targetCount) { return withdrawFromCupboard(UTENSIL_CUPBOARD_LOC, itemId, targetCount); } + private boolean withdrawFromFood(int itemId, int targetCount) { return withdrawFromCupboard(FOOD_CUPBOARD_LOC, itemId, targetCount); } + + private boolean withdrawFromCupboard(WorldPoint loc, int itemId, int targetCount) { + int have = count(itemId); + if (have >= targetCount) return true; + + int free = 28 - Rs2Inventory.count(); + int need = Math.min(targetCount - have, free); + if (need <= 0) { + log.warn("[mess] withdraw blocked: itemId={} target={} have={} free=0", itemId, targetCount, have); + return false; } - info("Walking to the Hosidius Servery area..."); - return Rs2Walker.walkTo(new WorldPoint(1645, 3627, 0), 4); - } - private BooleanSupplier cleanInventory() { - return () -> { - if (!Rs2Inventory.isEmpty()) { - if (Rs2Inventory.count() == 2 && Rs2Inventory.count(ItemID.KNIFE) == 2 && config.dish() == Dish.PIZZA) { - debug("Only knife in inventory, skipping inventory cleanup."); - return true; - } - - Set itemsToDrop = Set.of( - ItemID.BOWL_EMPTY, - ItemID.BOWL_WATER, - ItemID.KNIFE, - ItemID.HOSIDIUS_SERVERY_PIEDISH, - ItemID.BURNT_PIZZA, - ItemID.BURNT_PIE, - ItemID.BURNT_STEW, - ItemID.BURNT_MEAT, - ItemID.HOSIDIUS_SERVERY_RAW_MEAT, - ItemID.HOSIDIUS_SERVERY_PINEAPPLE, - ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, - ItemID.HOSIDIUS_SERVERY_TOMATO, - ItemID.HOSIDIUS_SERVERY_CHEESE, - ItemID.HOSIDIUS_SERVERY_POTATO, - ItemID.HOSIDIUS_SERVERY_POT_FLOUR, - ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, - ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, - ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, - ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, - ItemID.HOSIDIUS_SERVERY_PIE_SHELL, - ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, - ItemID.HOSIDIUS_SERVERY_MEATWATER - ); - - int droppedItemsCount = 0; - - info("Starting inventory cleanup. Total slots to check: 28"); - debug("Items to drop: " + itemsToDrop.toString()); - - // Iterate through inventory slots to drop items slot by slot - for (int slot = 0; slot < 28; slot++) { - if (!Rs2Inventory.isSlotEmpty(slot)) { - Rs2ItemModel item = Rs2Inventory.getItemInSlot(slot); - if (item != null) { - debug("Slot " + slot + ": Found item ID " + item.getId() + " (name: " + item.getName() + ")"); - if (itemsToDrop.contains(item.getId())) { - debug("Slot " + slot + ": Item ID " + item.getId() + " is in drop list, attempting to drop"); - Rs2Inventory.slotInteract(slot, "Drop"); - sleepGaussian(120, 40); - droppedItemsCount++; - debug("Slot " + slot + ": Drop action completed for item ID " + item.getId()); - } else { - debug("Slot " + slot + ": Item ID " + item.getId() + " NOT in drop list, keeping"); - } - } else { - debug("Slot " + slot + ": Item is null despite slot not being empty"); - } - } else { - debug("Slot " + slot + ": Empty slot, skipping"); - } - } - - info("Inventory cleanup completed. Total items dropped: " + droppedItemsCount); - - if (droppedItemsCount > 0) { - info("Dropped " + droppedItemsCount + " items from inventory."); - Rs2Antiban.actionCooldown(); - return false; - } - info("Walking to the bank to deposit items..."); - return Rs2Bank.bankItemsAndWalkBackToOriginalPosition( - Rs2Inventory.items().map(Rs2ItemModel::getName).collect(Collectors.toList()), - false, - BankLocation.HOSIDIUS_KITCHEN, - Rs2Player.getWorldLocation(), - 28, - 3 - ); + logDecision("withdraw[loc=" + loc + ",itemId=" + itemId + ",need=" + need + "]"); + setStatus("Getting supplies"); + + if (!Rs2GameObject.canReach(loc)) { log.info("[mess] walking to cupboard {}", loc); Rs2Walker.walkTo(loc, 4); return false; } + + if (!Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID)) { + // Exact-tile interact: cupboards at (1644,3624) and (1645,3623) are 1 tile apart, so a within(loc,1) + // query catches both and may interact with the wrong one. findObjectByLocation matches the exact tile. + boolean interacted = Rs2GameObject.interact(loc, "Search"); + log.info("[mess] cupboard interact(Search) at {} -> {}", loc, interacted); + if (!interacted) return false; + if (!sleepUntil(() -> Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID), 3000)) { + log.warn("[mess] shop widget never appeared after Search at {}", loc); + return false; } + } - debug("Inventory is clean, no action needed"); - return true; - }; - } + Widget shop = Rs2Widget.getWidget(SHOP_WIDGET_ID); + Widget[] children = shop != null ? shop.getDynamicChildren() : null; + if (children == null) { log.warn("[mess] shop widget has no children"); closeShop(); return false; } - private BooleanSupplier getUtensils() { - int itemId; - switch (getCurrentState()) { - case GET_EMPTY_BOWLS: - itemId = ItemID.BOWL_EMPTY; - break; - case GET_KNIFE: - if (Rs2Inventory.count(ItemID.KNIFE) >= 2) { - debug("Already have 2 knives, skipping getting knife."); - return () -> true; - } - itemId = ItemID.KNIFE; - break; - case GET_EMPTY_PIE_DISHES: - itemId = ItemID.HOSIDIUS_SERVERY_PIEDISH; - break; - default: - debug("Unknown state for getting utensils, cannot proceed."); - return () -> false; + int idx = -1; + for (int i = 0; i < children.length; i++) { + if (children[i].getItemId() == itemId) { idx = i; break; } } - return () -> { - if (Rs2GameObject.canReach(UTENSIL_CUPBOARD_LOC)) { - Rs2GameObject.interact(UTENSIL_CUPBOARD_LOC, "Search"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(15859715)); - if (Rs2Widget.isWidgetVisible(15859715)) { - Widget utensilShop = Rs2Widget.getWidget(15859715); - if (utensilShop != null) { - Widget[] children = utensilShop.getDynamicChildren(); - int index = IntStream.range(0, children.length) - .filter(i -> children[i].getItemId() == itemId) - .findFirst() - .orElse(-1); - if (index != -1) { - Rs2Widget.clickWidgetFast(children[index], index, 5); - sleepUntil(() -> Rs2Widget.hasWidget("Enter amount")); - if (Rs2Widget.hasWidget("Enter amount")) { - String amount; - switch (getCurrentState()) { - case GET_KNIFE: - amount = "2"; - break; - case GET_EMPTY_BOWLS: - if (config.dish() == Dish.PIZZA) { - amount = "13"; - break; - } - default: - amount = "14"; - } - Rs2Keyboard.typeString(amount); - Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); - Rs2Antiban.actionCooldown(); - sleepUntil(() -> closeMessShop()); - Rs2Inventory.waitForInventoryChanges(2000); - return Rs2Inventory.hasItem(itemId); - } - } - } - } + if (idx < 0) { + // Help diagnose mis-targeted cupboard / wrong item ID by logging what's actually on offer. + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Math.min(children.length, 16); i++) { + if (children[i].getItemId() > 0) sb.append(children[i].getItemId()).append(','); } + log.warn("[mess] item {} not in cupboard at {}; items found: [{}]", itemId, loc, sb); + closeShop(); return false; - }; - } + } - private BooleanSupplier fillBowl() { - return () -> { - if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, Rs2GameObject.getGameObject(SINK_LOC).getId()); - sleepUntil(() -> !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY), - 10000); - Rs2Antiban.actionCooldown(); - return true; - } - return false; - }; + log.info("[mess] clicking shop slot {} for itemId {} (qty {})", idx, itemId, need); + Rs2Widget.clickWidgetFast(children[idx], idx, 5); + if (!sleepUntil(() -> Rs2Widget.hasWidget("Enter amount"), 2000)) { log.warn("[mess] Enter amount widget never appeared"); closeShop(); return false; } + + // Mirror Rs2GrandExchange.setQuantity timing: the chatbox input doesn't reliably + // accept the value if you set it the same tick the widget appeared — small qty + // (e.g., 2) silently drops the value, large qty happens to race past the issue. + // typeString is unsafe here too: KEY_TYPED routes to canvas if the chatbox + // hasn't focused, leaking digits into game chat. Direct VarClientStr write + + // ~1s of sleep around it matches the pattern that works in GE. + sleep(600); + setChatboxAmount(need); + sleep(400); + Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); + + boolean got = sleepUntil(() -> count(itemId) >= targetCount, 4000); + log.info("[mess] withdraw result for itemId {}: have={} target={} ok={}", itemId, count(itemId), targetCount, got); + closeShop(); + return got; } - private BooleanSupplier getMeat() { - return () -> { - if (Rs2GameObject.canReach(MEAT_TABLE_LOC)) { - Rs2GameObject.interact(MEAT_TABLE_LOC, "Take-X"); - sleepUntil(() -> Rs2Widget.hasWidget("Enter amount")); - if (Rs2Widget.hasWidget("Enter amount")) { - Rs2Keyboard.typeString("14"); - Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); - Rs2Antiban.actionCooldown(); - } - Rs2Inventory.waitForInventoryChanges(2000); - Rs2Antiban.actionCooldown(); - return Rs2Inventory.hasItem(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); - } - return false; - }; + private boolean takeRawMeat(int targetCount) { + int have = count(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); + if (have >= targetCount) return true; + + int free = 28 - Rs2Inventory.count(); + int need = Math.min(targetCount - have, free); + if (need <= 0) return false; + + logDecision("takeRawMeat[need=" + need + "]"); + setStatus("Taking raw meat"); + if (!Rs2GameObject.canReach(MEAT_TABLE_LOC)) { Rs2Walker.walkTo(MEAT_TABLE_LOC, 4); return false; } + + boolean ok = Rs2GameObject.interact(MEAT_TABLE_LOC, "Take-X"); + log.info("[mess] meat-table interact(Take-X) -> {}", ok); + if (!ok) return false; + if (!sleepUntil(() -> Rs2Widget.hasWidget("Enter amount"), 2000)) { log.warn("[mess] Enter amount widget never appeared for raw meat"); return false; } + + // See withdrawFromCupboard for the timing rationale — small-qty values race past the input. + sleep(600); + setChatboxAmount(need); + sleep(400); + Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); + return sleepUntil(() -> count(ItemID.HOSIDIUS_SERVERY_RAW_MEAT) >= targetCount, 4000); } - private BooleanSupplier cook() { - int itemId; - switch (getCurrentState()) { - case USE_CLAY_OVEN: - itemId = ItemID.HOSIDIUS_SERVERY_RAW_MEAT; - break; - case FINISH_COOKING: - if (config.dish() == Dish.MEAT_PIE) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE; - } else if (config.dish() == Dish.STEW) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW; - } else if (config.dish() == Dish.PIZZA) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA; - } else { - debug("Unknown dish selected, cannot finish cooking."); - return () -> false; - } - break; - default: - debug("Unknown state for cooking, cannot proceed."); - return () -> false; - } - return () -> { - if (Rs2GameObject.canReach(CLAY_OVEN_LOC)) { - Rs2Inventory.useItemOnObject(itemId, Rs2GameObject.getGameObject(CLAY_OVEN_LOC).getId()); - sleepUntil(() -> Rs2Widget.hasWidget("How many would you like to cook?")); - if (Rs2Widget.hasWidget("How many would you like to cook?")) { - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - Rs2Antiban.actionCooldown(); - } - Rs2Inventory.waitForInventoryChanges(5000); - sleepUntil(() -> !Rs2Player.isAnimating(1000), 50000); - Rs2Antiban.actionCooldown(); - } - return false; - }; + private boolean fillBowls() { + if (!has(ItemID.BOWL_EMPTY)) return true; + logDecision("fillBowls"); + setStatus("Filling bowls"); + if (!Rs2GameObject.canReach(SINK_LOC)) { Rs2Walker.walkTo(SINK_LOC, 4); return false; } + + TileObject sink = Rs2GameObject.findObjectByLocation(SINK_LOC); + if (sink == null) { log.warn("[mess] sink not found at {}", SINK_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, sink.getId()); + log.info("[mess] use bowl on sink id={} -> {}", sink.getId(), used); + return sleepUntil(() -> !has(ItemID.BOWL_EMPTY), 15000); + } + private boolean returnEmptyBowls() { + if (!has(ItemID.BOWL_EMPTY)) return true; + logDecision("returnEmptyBowls"); + setStatus("Returning empty bowls"); + if (!Rs2GameObject.canReach(UTENSIL_CUPBOARD_LOC)) { Rs2Walker.walkTo(UTENSIL_CUPBOARD_LOC, 4); return false; } + + TileObject cupboard = Rs2GameObject.findObjectByLocation(UTENSIL_CUPBOARD_LOC); + if (cupboard == null) { log.warn("[mess] utensil cupboard not found at {}", UTENSIL_CUPBOARD_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, cupboard.getId()); + log.info("[mess] return bowl on cupboard id={} -> {}", cupboard.getId(), used); + return sleepUntil(() -> !has(ItemID.BOWL_EMPTY), 8000); } - private BooleanSupplier combineItems() { - int item1; - int item2; - switch (getCurrentState()) { - case COMBINE_PASTRY_DOUGH: - case COMBINE_PIZZA_BASE: - item1 = ItemID.BOWL_WATER; - item2 = ItemID.HOSIDIUS_SERVERY_POT_FLOUR; - break; - case COMBINE_PIE_SHELL: - item1 = ItemID.HOSIDIUS_SERVERY_PIEDISH; - item2 = ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH; - break; - case COMBINE_MEAT_PIE: - item1 = ItemID.HOSIDIUS_SERVERY_PIE_SHELL; - item2 = ItemID.HOSIDIUS_SERVERY_COOKED_MEAT; - break; - case COMBINE_MEAT_WATER: - item1 = ItemID.BOWL_WATER; - item2 = ItemID.HOSIDIUS_SERVERY_COOKED_MEAT; - break; - case COMBINE_STEW: - item1 = ItemID.HOSIDIUS_SERVERY_MEATWATER; - item2 = ItemID.HOSIDIUS_SERVERY_POTATO; - break; - case COMBINE_PIZZA_TOMATO: - item1 = ItemID.HOSIDIUS_SERVERY_PIZZA_BASE; - item2 = ItemID.HOSIDIUS_SERVERY_TOMATO; - break; - case COMBINE_PIZZA_CHEESE: - item1 = ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA; - item2 = ItemID.HOSIDIUS_SERVERY_CHEESE; - break; - case COMBINE_PIZZA_PINEAPPLE: - item1 = ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA; - item2 = ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS; - break; - case CUT_PINEAPPLE: - item1 = ItemID.KNIFE; - item2 = ItemID.HOSIDIUS_SERVERY_PINEAPPLE; - break; - default: - debug("Unknown state for combining items, cannot proceed."); - return () -> false; + /** + * Cook all of {@code rawId} on the clay oven in one phase: issue the cook + * (Make-All via SPACE on the production widget), then BLOCK until the raw + * ingredient is fully consumed. Returning mid-chain lets the dispatcher + * pick a different guard (e.g., a combine that depends on the partial + * cooked-meat output), interrupting cooking — this is what the user + * explicitly forbids. + */ + private boolean cookOnOven(int rawId) { + if (!has(rawId)) return true; + logDecision("cookOnOven[rawId=" + rawId + "]"); + setStatus("Cooking"); + if (!Rs2GameObject.canReach(CLAY_OVEN_LOC)) { Rs2Walker.walkTo(CLAY_OVEN_LOC, 4); return false; } + + int before = count(rawId); + + if (Rs2Widget.isProductionWidgetOpen()) { + // Dialog already up from a previous tick — confirm Cook-All. + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } else { + TileObject oven = Rs2GameObject.findObjectByLocation(CLAY_OVEN_LOC); + if (oven == null) { log.warn("[mess] clay oven not found at {}", CLAY_OVEN_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(rawId, oven.getId()); + log.info("[mess] use {} on oven id={} -> {}", rawId, oven.getId(), used); + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 2500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } } - return () -> { - if (Rs2Inventory.hasItem(item1) && Rs2Inventory.hasItem(item2)) { - boolean alreadyInRightSlots = (Rs2Inventory.slotContains(26, item1) && Rs2Inventory.slotContains(27, item2)) || - (Rs2Inventory.slotContains(26, item2) && Rs2Inventory.slotContains(27, item1)); - - if (!alreadyInRightSlots) { - Rs2ItemModel lastOfItem1 = Rs2Inventory.getLast(item1); - Rs2ItemModel lastOfItem2 = Rs2Inventory.getLast(item2); - - if (lastOfItem1 != null && lastOfItem2 != null) { - if (lastOfItem1.getSlot() != 26 && lastOfItem1.getSlot() != 27) { - if (lastOfItem2.getSlot() == 26) { - Rs2Inventory.moveItemToSlot(lastOfItem1, 27); - } else if (lastOfItem2.getSlot() == 27) { - Rs2Inventory.moveItemToSlot(lastOfItem1, 26); - } else { - Rs2Inventory.moveItemToSlot(lastOfItem1, 26); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - Rs2Inventory.moveItemToSlot(lastOfItem2, 27); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - } - } - } else { - debug("Failed to find items in inventory, cannot combine."); - return false; - } - } - - Widget item1Widget = Rs2Inventory.getInventoryWidget().getChild(26); - Widget item2Widget = Rs2Inventory.getInventoryWidget().getChild(27); - if (getCurrentState() == State.COMBINE_PASTRY_DOUGH || getCurrentState() == State.COMBINE_PIZZA_BASE) { - String option = (getCurrentState() == State.COMBINE_PASTRY_DOUGH) ? "Pastry dough" : "Pizza base"; - Rs2Widget.clickWidget(item1Widget); - Rs2Widget.clickWidget(item2Widget); - Rs2Dialogue.sleepUntilSelectAnOption(); - Rs2Dialogue.keyPressForDialogueOption(option); - sleepUntil(() -> !Rs2Inventory.hasItem(item1), 30000); - Rs2Antiban.actionCooldown(); - return true; - } else if (getCurrentState() == State.CUT_PINEAPPLE) { - while (Rs2Inventory.hasItem(ItemID.HOSIDIUS_SERVERY_PINEAPPLE) && canContinue()) { - Rs2Widget.clickWidget(item1Widget); - sleepGaussian(120, 40); - Rs2Widget.clickWidget(item2Widget); - sleepGaussian(120, 40); - } - - Rs2ItemModel knife = Rs2Inventory.getLast(ItemID.KNIFE); - if (knife != null) { - Rs2Inventory.moveItemToSlot(knife, (Rs2Inventory.slotContains(0, ItemID.KNIFE)) ? 1 : 0); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - return true; - } else { - debug("Failed to find knife in inventory, cannot move knife."); - return false; - } - } else { - while (Rs2Inventory.hasItem(item1) && canContinue()) { - Rs2Widget.clickWidget(item1Widget); - sleepGaussian(120, 40); - Rs2Widget.clickWidget(item2Widget); - sleepGaussian(120, 40); - } - return true; - } - } - return false; - }; + // Block until raw is fully consumed. Per-cook ~3 ticks (~1.8s), so an + // 8s stall guard is generous; hard cap 90s covers a 14-batch comfortably. + boolean done = waitForDepleted(rawId, before, 8000); + log.info("[mess] cookOnOven id={} done={} ({}->{})", rawId, done, before, count(rawId)); + return true; } - private BooleanSupplier getFood() { - int itemId; - switch (getCurrentState()) { - case GET_POTATOES: - itemId = ItemID.HOSIDIUS_SERVERY_POTATO; - break; - case GET_PINEAPPLES: - itemId = ItemID.HOSIDIUS_SERVERY_PINEAPPLE; - break; - case GET_TOMATOES: - itemId = ItemID.HOSIDIUS_SERVERY_TOMATO; - break; - case GET_CHEESE: - itemId = ItemID.HOSIDIUS_SERVERY_CHEESE; - break; - case GET_FLOUR: - itemId = ItemID.HOSIDIUS_SERVERY_POT_FLOUR; - break; - default: - debug("Unknown state for getting food, cannot proceed."); - return () -> false; - } - return () -> { - if (Rs2GameObject.canReach(FOOD_CUPBOARD_LOC)) { - Rs2GameObject.interact(FOOD_CUPBOARD_LOC, "Search"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(15859715)); - if (Rs2Widget.isWidgetVisible(15859715)) { - Widget foodShop = Rs2Widget.getWidget(15859715); - if (foodShop != null) { - Widget[] children = foodShop.getDynamicChildren(); - int index = IntStream.range(0, children.length) - .filter(i -> children[i].getItemId() == itemId) - .findFirst() - .orElse(-1); - if (index != -1) { - Rs2Widget.clickWidgetFast(children[index], index, 4); - Rs2Inventory.waitForInventoryChanges(2000); - sleepUntil(() -> closeMessShop()); - Rs2Antiban.actionCooldown(); - return Rs2Inventory.hasItem(itemId); - } - } - } + /** Set the chatbox numeric input field directly. Avoids focus-routing race in typeString. */ + private static void setChatboxAmount(int amount) { + Microbot.getClientThread().runOnClientThreadOptional(() -> { + Widget input = Rs2Widget.getWidget(InterfaceID.Chatbox.MES_TEXT2); + if (input != null) { + input.setText(amount + "*"); } - return false; - }; + Microbot.getClient().setVarcStrValue(VarClientStr.INPUT_TEXT, String.valueOf(amount)); + return null; + }); } - private void setOrderOfStates() { - switch (config.dish()) { - case MEAT_PIE: - State.GETTING_READY.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.GET_FLOUR); - State.GET_FLOUR.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.COMBINE_PASTRY_DOUGH); - State.COMBINE_PASTRY_DOUGH.setNext(State.RETURN_EMPTY_BOWLS); - State.RETURN_EMPTY_BOWLS.setNext(State.GET_EMPTY_PIE_DISHES); - State.GET_EMPTY_PIE_DISHES.setNext(State.COMBINE_PIE_SHELL); - State.COMBINE_PIE_SHELL.setNext(State.GET_MEAT); - State.GET_MEAT.setNext(State.USE_CLAY_OVEN); - State.USE_CLAY_OVEN.setNext(State.COMBINE_MEAT_PIE); - State.COMBINE_MEAT_PIE.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.USE_BUFFET_TABLE); - break; - case STEW: - State.GETTING_READY.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.GET_MEAT); - State.GET_MEAT.setNext(State.USE_CLAY_OVEN); - State.USE_CLAY_OVEN.setNext(State.COMBINE_MEAT_WATER); - State.COMBINE_MEAT_WATER.setNext(State.GET_POTATOES); - State.GET_POTATOES.setNext(State.COMBINE_STEW); - State.COMBINE_STEW.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.USE_BUFFET_TABLE); - break; - case PIZZA: - State.GETTING_READY.setNext(State.GET_KNIFE); - State.GET_KNIFE.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.GET_FLOUR); - State.GET_FLOUR.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.COMBINE_PIZZA_BASE); - State.COMBINE_PIZZA_BASE.setNext(State.RETURN_EMPTY_BOWLS); - State.RETURN_EMPTY_BOWLS.setNext(State.GET_TOMATOES); - State.GET_TOMATOES.setNext(State.COMBINE_PIZZA_TOMATO); - State.COMBINE_PIZZA_TOMATO.setNext(State.GET_CHEESE); - State.GET_CHEESE.setNext(State.COMBINE_PIZZA_CHEESE); - State.COMBINE_PIZZA_CHEESE.setNext(State.GET_PINEAPPLES); - State.GET_PINEAPPLES.setNext(State.CUT_PINEAPPLE); - State.CUT_PINEAPPLE.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.COMBINE_PIZZA_PINEAPPLE); - State.COMBINE_PIZZA_PINEAPPLE.setNext(State.USE_BUFFET_TABLE); - break; - default: - debug("Unknown dish selected, cannot set order of states."); + /** + * Combine two items with Make-All semantics. + *

+ * Issues one combine, presses SPACE on the Make-X production widget, then + * waits for one ingredient to deplete. Combine actions in this minigame + * have no sustained player animation (the bowl/pot/dish recipes are pure + * inventory transformations), so we cannot use {@code isAnimating} to + * detect the chain — we wait on {@link #count} change with a stall guard. + */ + private boolean combineAll(int item1, int item2, String statusText) { + if (!has(item1) || !has(item2)) return true; + logDecision("combineAll[" + item1 + "+" + item2 + "]"); + setStatus(statusText); + int before1 = count(item1); + int before2 = count(item2); + + Rs2Inventory.combine(item1, item2); + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 1500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); } - State.USE_BUFFET_TABLE.setNext(State.WAITING); - State.WAITING.setNext(State.GETTING_READY); - State.HOP_WORLD.setNext(State.USE_BUFFET_TABLE); + boolean done = waitForCombineChain(item1, item2, before1, before2); + log.info("[mess] combineAll {} + {} -> done={} ({}->{} / {}->{})", + item1, item2, done, before1, count(item1), before2, count(item2)); + return true; } - private boolean closeMessShop() { - if (!Rs2Settings.isEscCloseInterfaceSettingEnabled()){ - closeWithESCKey(); - } else { - Widget w = Rs2Widget.getWidget(15859713); - if (w == null) { - debug("Closing button was not found, trying to close the shop using ESC key."); - closeWithESCKey(); - } else { - Widget[] children = w.getChildren(); - if (children != null && children.length > 0) { - Rs2Widget.clickWidget(children[children.length - 1]); - } else { - debug("No children found in the widget, trying to close the shop using ESC key."); - closeWithESCKey(); - } - } + /** + * Bowl-water + pot-flour combine: first pops a "Select an option" chat + * dialog (Pastry dough vs Pizza base), then a Make-X widget. Picks the + * option, presses SPACE for All, then blocks for full chain completion. + */ + private boolean combineDoughDialog(int item1, int item2, String dialogOption) { + if (!has(item1) || !has(item2)) return true; + logDecision("combineDough[" + dialogOption + "]"); + setStatus("Making " + dialogOption); + int before1 = count(item1); + int before2 = count(item2); + + Rs2Inventory.combine(item1, item2); + if (Rs2Dialogue.sleepUntilSelectAnOption()) { + Rs2Dialogue.keyPressForDialogueOption(dialogOption); + } + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 1500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); } + boolean done = waitForCombineChain(item1, item2, before1, before2); + log.info("[mess] combineDough {} + {} -> done={} (flour {}->{})", + item1, item2, done, before2, count(item2)); return true; } - private void closeWithESCKey() { - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - if (!Rs2Inventory.isOpen()) { - Rs2Inventory.open(); + /** + * Wait for a combine chain to consume an ingredient. Returns when either + * {@code item1} or {@code item2} reaches 0. Stall guard: returns false if + * the combined count hasn't dropped for 5s. Hard cap 60s. + *

+ * Tracks the SUM of counts so any per-action depletion registers as + * progress. A min-based guard breaks for combines where one ingredient + * persists (e.g., knife+pineapple — knife stays at 2 while pineapple + * counts down, so min stays pinned at 2 and the stall trips early). + */ + private boolean waitForCombineChain(int item1, int item2, int before1, int before2) { + long start = System.currentTimeMillis(); + int lastSum = before1 + before2; + long lastChangeMs = start; + while (System.currentTimeMillis() - start < 60_000) { + int c1 = count(item1); + int c2 = count(item2); + if (c1 == 0 || c2 == 0) return true; + int currentSum = c1 + c2; + if (currentSum < lastSum) { + lastSum = currentSum; + lastChangeMs = System.currentTimeMillis(); + } else if (System.currentTimeMillis() - lastChangeMs > 5000) { + return false; + } + sleep(300); } + return false; } - private boolean canContinue() { - return isRunning() && super.isRunning() && Microbot.isLoggedIn() && !BreakHandlerScript.isBreakActive() && super.run(); - } - - private void info(String message) { - Microbot.log(Level.INFO, message); + /** + * Wait for a single inventory item to be fully consumed (count → 0). + * Stall guard {@code stallMs}: returns false if the count hasn't dropped + * for that long. Hard cap 90s. + */ + private boolean waitForDepleted(int itemId, int beforeCount, long stallMs) { + long start = System.currentTimeMillis(); + int lastCount = beforeCount; + long lastChangeMs = start; + while (System.currentTimeMillis() - start < 90_000) { + int now = count(itemId); + if (now == 0) return true; + if (now < lastCount) { + lastCount = now; + lastChangeMs = System.currentTimeMillis(); + } else if (System.currentTimeMillis() - lastChangeMs > stallMs) { + return false; + } + sleep(400); + } + return false; } - private void debug(String message) { - Microbot.log(Level.DEBUG, message); + /** + * Hard-fail invariant: no chain item should ever exceed {@link #BATCH_SIZE} + * (or {@link #PIZZA_BATCH_SIZE} for pizza). Exceeding it means our top-up + * math overshot or a leftover state went unhandled — we'd start over-fetching + * ingredients we'll never use, so stop loudly rather than waste resources. + */ + private boolean violatesBatchInvariant() { + int max = config.dish() == Dish.PIZZA ? PIZZA_BATCH_SIZE : BATCH_SIZE; + for (int id : chainItemsForDish()) { + int c = count(id); + if (c > max) { + log.error("[mess] BATCH INVARIANT VIOLATED: itemId={} count={} > max={} for dish={}", + id, c, max, config.dish()); + return true; + } + } + return false; } - @Override - public void shutdown() { - super.shutdown(); - setCurrentState(State.WAITING); - if (mainScheduledFuture != null && !mainScheduledFuture.isCancelled()) { - mainScheduledFuture.cancel(true); + private int[] chainItemsForDish() { + switch (config.dish()) { + case STEW: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_MEATWATER, ItemID.HOSIDIUS_SERVERY_POTATO, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, ItemID.HOSIDIUS_SERVERY_STEW, + }; + case MEAT_PIE: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIEDISH, ItemID.HOSIDIUS_SERVERY_PIE_SHELL, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_MEAT_PIE, + }; + case PIZZA: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_TOMATO, + ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, ItemID.HOSIDIUS_SERVERY_CHEESE, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA, + }; } - debug("The Mess script has been shut down."); + return new int[0]; } - public enum State { - WAITING("Waiting", null), - GETTING_READY("Getting ready", null), - - GET_EMPTY_BOWLS("Getting empty bowls", null), - GET_EMPTY_PIE_DISHES("Getting empty pie dishes", null), - GET_KNIFE("Getting knife", null), - - RETURN_EMPTY_BOWLS("Returning empty bowls", null), - - GET_MEAT("Getting raw meat", null), + /** + * Drop any burnt food, but only between phases — never mid-cook. + * Dropping during a Cook-All chain interrupts the player's cooking animation + * and breaks the chain, so we gate on "not currently cooking." + */ + private void dropBurnt() { + if (!has(ItemID.BURNT_MEAT) && !has(ItemID.BURNT_PIE) && !has(ItemID.BURNT_STEW) && !has(ItemID.BURNT_PIZZA)) return; + if (Rs2Player.isAnimating(3500) || Rs2Widget.isProductionWidgetOpen()) return; + log.info("[mess] dropping burnt food"); + Rs2Inventory.dropAll(ItemID.BURNT_MEAT, ItemID.BURNT_PIE, ItemID.BURNT_STEW, ItemID.BURNT_PIZZA); + } - GET_POTATOES("Getting supplies", null), - GET_PINEAPPLES("Getting supplies", null), - GET_TOMATOES("Getting supplies", null), - GET_CHEESE("Getting supplies", null), - GET_FLOUR("Getting supplies", null), + private void serveAndMaybeHop(int appreciationWidgetId) { + if (isUnderAppreciationThreshold(appreciationWidgetId)) { + hopWorld(); + return; + } + if (!Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { Rs2Walker.walkTo(BUFFET_TABLE_LOC, 4); return; } - USE_SINK("Using sink", null), + logDecision("serve"); + setStatus("Serving"); + boolean ok = Rs2GameObject.interact(BUFFET_TABLE_LOC, "Serve"); + log.info("[mess] buffet interact(Serve) -> {}", ok); + Rs2Player.waitForXpDrop(Skill.COOKING, 2500, false); + } - USE_CLAY_OVEN("Cooking", null), - FINISH_COOKING("Finishing cooking", null), + private boolean isUnderAppreciationThreshold(int widgetId) { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + Widget bar = Rs2Widget.getWidget(widgetId); + if (bar == null) return false; + Widget filled = bar.getChild(0); + if (filled == null || bar.getWidth() <= 0) return false; + int pct = filled.getWidth() * 100 / bar.getWidth(); + return pct < config.appreciation_threshold(); + }).orElse(false); + } - COMBINE_PASTRY_DOUGH("Combining ingredients", null), - COMBINE_PIE_SHELL("Combining ingredients", null), - COMBINE_MEAT_PIE("Combining ingredients", null), + private void hopWorld() { + setStatus("Hopping worlds"); + int currentWorld = Microbot.getClient().getWorld(); + Microbot.hopToWorld(Login.getRandomWorld(true)); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING, 5000); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN, 15000); + if (Microbot.getClient().getWorld() == currentWorld) { + log.warn("World hop failed; will retry next tick."); + } + } - COMBINE_MEAT_WATER("Combining ingredients", null), - COMBINE_STEW("Combining ingredients", null), + // ---------- inventory cleanup ---------- + + private static final Set JUNK_ITEMS = Set.of( + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, ItemID.KNIFE, + ItemID.HOSIDIUS_SERVERY_PIEDISH, + ItemID.BURNT_PIZZA, ItemID.BURNT_PIE, ItemID.BURNT_STEW, ItemID.BURNT_MEAT, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, + ItemID.HOSIDIUS_SERVERY_TOMATO, ItemID.HOSIDIUS_SERVERY_CHEESE, ItemID.HOSIDIUS_SERVERY_POTATO, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, + ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, ItemID.HOSIDIUS_SERVERY_PIE_SHELL, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, ItemID.HOSIDIUS_SERVERY_MEATWATER, + // Cooked dishes — drop these too rather than walking to the bank with them + ItemID.HOSIDIUS_SERVERY_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_STEW, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA + ); + + /** Inventory has any item that isn't (a) currently mid-batch (handled by tick predicates) or (b) reserved for the active dish. */ + private boolean hasJunk() { + if (Rs2Inventory.isEmpty()) return false; + // The per-dish flow handles every Hosidius/bowl/knife item. Junk = anything outside JUNK_ITEMS that we shouldn't process. + // Conversely, if everything in inventory IS in JUNK_ITEMS we don't need to clean — let the dish flow consume it. + // We only clean when there's a non-Mess item that the script can't progress with. + return Rs2Inventory.items().anyMatch(item -> !JUNK_ITEMS.contains(item.getId())); + } - COMBINE_PIZZA_BASE("Combining ingredients", null), - COMBINE_PIZZA_TOMATO("Combining ingredients", null), - COMBINE_PIZZA_CHEESE("Combining ingredients", null), - COMBINE_PIZZA_PINEAPPLE("Combining ingredients", null), + private void runCleanInventory() { + setStatus("Cleaning inventory"); + // Drop everything we recognize first (faster than banking). + int dropped = 0; + for (int slot = 0; slot < 28; slot++) { + if (Rs2Inventory.isSlotEmpty(slot)) continue; + Rs2ItemModel item = Rs2Inventory.getItemInSlot(slot); + if (item != null && JUNK_ITEMS.contains(item.getId())) { + Rs2Inventory.slotInteract(slot, "Drop"); + sleepGaussian(120, 40); + dropped++; + } + } + if (dropped > 0) return; + + // Anything left is unrecognized. Bank it. + if (Rs2Inventory.isEmpty()) return; + log.info("Banking {} unrecognized items at Hosidius Kitchen.", Rs2Inventory.count()); + Rs2Bank.bankItemsAndWalkBackToOriginalPosition( + Rs2Inventory.items().map(Rs2ItemModel::getName).collect(Collectors.toList()), + false, + BankLocation.HOSIDIUS_KITCHEN, + Rs2Player.getWorldLocation(), + 28, + 3 + ); + } - CUT_PINEAPPLE("Cutting the pineapples", null), + // ---------- shop close ---------- - USE_BUFFET_TABLE("Serving the food", null), - HOP_WORLD("Hopping worlds", null); + private void closeShop() { + if (!Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID)) return; + Widget closeParent = Rs2Widget.getWidget(SHOP_CLOSE_PARENT); + Widget[] kids = closeParent != null ? closeParent.getChildren() : null; + if (kids != null && kids.length > 0) { + Rs2Widget.clickWidget(kids[kids.length - 1]); + } else { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + } + sleepUntil(() -> !Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID), 1500); + } + // ---------- helpers ---------- - @Getter - private final String status; + private boolean has(int id) { return Rs2Inventory.hasItem(id); } + private int count(int id) { return Rs2Inventory.count(id); } - @Setter - @Getter - private State next; + private void setStatus(String s) { + if (overlay != null) overlay.setStatus(s); + } - State(String status, State next) { - this.status = status; - this.next = next; + @Override + public void shutdown() { + super.shutdown(); + if (mainScheduledFuture != null && !mainScheduledFuture.isCancelled()) { + mainScheduledFuture.cancel(true); } } @@ -781,11 +669,7 @@ public enum Dish { STEW("Servery Stew"), PIZZA("Servery Pineapple Pizza"); - @Getter - private final String name; - - Dish(String name) { - this.name = name; - } + @Getter private final String name; + Dish(String name) { this.name = name; } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java index 382a5432de..46b8df1b23 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.microhunter.scripts; -import net.runelite.api.GameObject; import net.runelite.api.ItemID; import net.runelite.api.ObjectID; import net.runelite.api.coords.WorldPoint; @@ -8,10 +7,9 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.microhunter.AutoHunterConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.ArrayList; @@ -89,15 +87,12 @@ public void shutdown() { private void handleIdleState() { try { - // If there are box traps on the floor, interact with them first - if (Rs2GroundItem.interact(ItemID.BOX_TRAP, "lay", 4)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.BOX_TRAP).within(4).interact("lay")) { currentState = State.LAYING; return; } - // If our inventory is full of ferrets if (Rs2Inventory.emptySlotCount() <= 1 && Rs2Inventory.contains(ItemID.FERRET)) { - // ferrets have the option release and not drop while (Rs2Inventory.contains(ItemID.FERRET)) { Rs2Inventory.interact(ItemID.FERRET, "Release"); sleep(0, 750); @@ -109,24 +104,25 @@ private void handleIdleState() { return; } - // If there are shaking boxes, interact with them. ferrets - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9384, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9384).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // If there are shaking boxes, interact with them - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9383, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9383).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // If there are shaking boxes, interact with them - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9382, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9382).within(4).interact("reset")) { + currentState = State.CATCHING; + return; + } + // Black chinchompa shaking box + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // Interact with traps that have not caught anything - if (Rs2GameObject.interact(ObjectID.BOX_TRAP_9385, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.BOX_TRAP_9385).within(4).interact("reset")) { currentState = State.CATCHING; } } catch (Exception ex) { @@ -152,9 +148,8 @@ private void handleLayingState(AutoHunterConfig config) { } public void handleBreaks() { - int secondsUntilBreak = BreakHandlerScript.breakIn; // Time until the break + int secondsUntilBreak = BreakHandlerScript.breakIn; - //Clear list incase user changed trap layout This should run about 2-4 minutes before break if (secondsUntilBreak > 61 && secondsUntilBreak < 200) { if (!boxtiles.isEmpty()) { boxtiles.clear(); @@ -162,43 +157,33 @@ public void handleBreaks() { } if (secondsUntilBreak > 0 && secondsUntilBreak <= 60) { - // We're going on break in 1 minute or less. - // Save Trap locations for (int trapId : trapIds) { - List gameObjects = Rs2GameObject.getGameObjects(obj -> obj.getId() == trapId); - if (gameObjects != null) { - for (GameObject gameObject : gameObjects) { - if (gameObject != null) { - WorldPoint location = gameObject.getWorldLocation(); - if (Rs2Player.getWorldLocation().distanceTo(location) > 5) { - continue; // Skip traps beyond the range - } - if (!boxtiles.contains(location)) { - boxtiles.add(location); - } - } + var gameObjects = Microbot.getRs2TileObjectCache().query().withId(trapId).toList(); + for (Rs2TileObjectModel gameObject : gameObjects) { + WorldPoint location = gameObject.getWorldLocation(); + if (Rs2Player.getWorldLocation().distanceTo(location) > 5) { + continue; + } + if (!boxtiles.contains(location)) { + boxtiles.add(location); } } } - // At this point, boxtiles should be populated with the world points of the old traps. - - // Dismantling traps for our break. for (WorldPoint oldTile : boxtiles) { - if (Rs2GameObject.getGameObject(oldTile) != null) { - //Dismantle or Reset + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).first() != null) { if (Rs2Player.getWorldLocation().distanceTo(oldTile) > 5) { - continue; // Skip traps beyond the range + continue; } - while (Rs2GameObject.getGameObject(oldTile) != null) { + while (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).first() != null) { if (Rs2Player.getWorldLocation().distanceTo(oldTile) > 5) { - break; // Skip traps beyond the range + break; } - if (Rs2GameObject.interact(oldTile, "Dismantle")) { + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).interact("Dismantle")) { sleep(1000, 3000); break; } - if (Rs2GameObject.interact(oldTile, "Reset")) { + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).interact("Reset")) { sleep(1000, 3000); break; } @@ -208,16 +193,12 @@ public void handleBreaks() { oneRun = true; } - //We're back from our break if (secondsUntilBreak > 60 && oneRun) { if (!boxtiles.isEmpty()) { - //Setting traps down for (WorldPoint LayTrapTile : boxtiles) { - if (Rs2GameObject.getGameObject(LayTrapTile) != null) { - //There's already an object there do nothing + if (Microbot.getRs2TileObjectCache().query().within(LayTrapTile, 0).first() != null) { } else { - //we need to get to the tile if (!Rs2Player.getWorldLocation().equals(LayTrapTile)) { while (!Rs2Player.getWorldLocation().equals(LayTrapTile)) { Microbot.log("Walking to trap tile"); @@ -225,17 +206,16 @@ public void handleBreaks() { sleep(1000, 3000); } } - //we need to put a trap. Microbot.log("Placing trap"); int maxTries = 0; - while (Rs2GameObject.getGameObject(LayTrapTile) == null) { - if (!Rs2GroundItem.exists("Box trap", 6)) { + while (Microbot.getRs2TileObjectCache().query().within(LayTrapTile, 0).first() == null) { + if (Microbot.getRs2TileItemCache().query().withName("Box trap").within(6).count() == 0) { if (Rs2Inventory.contains("Box trap")) { Rs2Inventory.interact("Box trap", "Lay"); sleep(4000, 6000); } } else { - Rs2GroundItem.take("Box trap", 6); + Microbot.getRs2TileItemCache().query().withName("Box trap").within(6).interact("Take"); sleep(4000, 6000); } if (maxTries >= 3) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java index 0c34142012..01f2345ecc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java @@ -86,6 +86,17 @@ default int maxPlayersInArea() { return 0; } + @ConfigItem( + keyName = "leagueMode", + name = "League mode (anti-AFK)", + description = "Periodically presses a key to reset the idle timer so you never get logged out", + position = 4, + section = generalSection + ) + default boolean leagueMode() { + return false; + } + @ConfigItem( keyName = "UseBank", name = "UseBank", diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java index 43f6db18b6..20384f2f44 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java @@ -24,7 +24,7 @@ ) @Slf4j public class AutoMiningPlugin extends Plugin { - public static final String version = "1.0.11"; + public static final String version = "1.0.12"; @Inject private AutoMiningConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java index 3f3d861dde..8bd0eaad07 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java @@ -19,12 +19,14 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.ArrayList; +import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -54,11 +56,20 @@ public boolean run(AutoMiningConfig config) { try { if (!super.run()) return; if (!Microbot.isLoggedIn()) return; + if (config.leagueMode() && Rs2Player.checkIdleLogout(Rs2Random.between(500, 1500))) { + int[] arrowKeys = { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN }; + Rs2Keyboard.keyPress(arrowKeys[Rs2Random.between(0, arrowKeys.length - 1)]); + } if (Rs2AntibanSettings.actionCooldownActive) return; if (initialPlayerLocation == null) { initialPlayerLocation = Rs2Player.getWorldLocation(); } + // Skip cycle if we don't have a valid location + if (initialPlayerLocation == null) { + return; + } + updateActiveRock(config); if (config.progressiveMode() && ensureProgressiveLocation(config)) { @@ -120,6 +131,16 @@ public boolean run(AutoMiningConfig config) { return; } + // Check if we're too far from mining location - walk back first + if (initialPlayerLocation != null) { + int distanceFromStart = Rs2Player.getWorldLocation().distanceTo(initialPlayerLocation); + if (distanceFromStart > config.distanceToStray()) { + Microbot.status = "Walking back to mining location..."; + Rs2Walker.walkTo(initialPlayerLocation, config.distanceToStray()); + return; + } + } + GameObject rock = Rs2GameObject.findReachableObject(activeRock.getName(), true, config.distanceToStray(), initialPlayerLocation); if (rock != null) { @@ -252,7 +273,9 @@ private boolean ensureProgressiveLocation(AutoMiningConfig config) { WorldPoint targetPoint = activeLocation.getWorldPoint(); - if (initialPlayerLocation == null || !initialPlayerLocation.equals(targetPoint)) { + // Only update initialPlayerLocation if it's null + // Don't update just because player is far away (e.g., at bank) - that breaks return-to-location + if (initialPlayerLocation == null) { initialPlayerLocation = targetPoint; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java b/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java index 65b9342fb6..307e29e558 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java @@ -131,8 +131,7 @@ public boolean hasRequirements() { // bolt ammo slot ? when the ids is any ammo if (numberOfItems+numberOfItemsInPouch +numberOfItemsInBank< requiredAmount) { - log.warn("Missing required item: {} x{} (have {})", itemId, requiredAmount, numberOfItems); - Microbot.log("Missing required item: " + itemId + " x" + requiredAmount + " (have " + numberOfItems + ")"); + log.debug("Missing required item: {} x{} (have {})", itemId, requiredAmount, numberOfItems); return false; } return true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java b/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java index dd985e9f41..ad4e7e3b3f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java @@ -22,7 +22,8 @@ public enum Rocks { URT_SALT("Urt salt rocks", 72), EFH_SALT("Efh salt rocks", 72), TE_SALT("Te salt rocks", 72), - RUNITE("runite rocks", 85); + RUNITE("runite rocks", 85), + NONE("None", 1); private final String name; private final int miningLevel; diff --git a/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java index 813c4ddd45..2ad069d752 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java @@ -10,7 +10,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -53,8 +53,8 @@ public boolean run() { TARGET_SPOT_ID = FISHING_SPOT_1_ID; } Microbot.status = "DODGING FLYING FISH"; - fishingspot = Rs2Npc.getNpc(TARGET_SPOT_ID); - Rs2Npc.interact(fishingspot, "Small Net"); + fishingspot = Microbot.getRs2NpcCache().query().withId(TARGET_SPOT_ID).nearest(); + if (fishingspot != null) fishingspot.click("Small Net"); Rs2Antiban.actionCooldown(); return; } @@ -63,8 +63,8 @@ public boolean run() { } Microbot.status = "INTERACTING"; - fishingspot = Rs2Npc.getNpc(TARGET_SPOT_ID); - Rs2Npc.interact(fishingspot, "Small Net"); + fishingspot = Microbot.getRs2NpcCache().query().withId(TARGET_SPOT_ID).nearest(); + if (fishingspot != null) fishingspot.click("Small Net"); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java b/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java index d200d3c500..8ebd6db6c2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java @@ -5,7 +5,6 @@ import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; - import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -15,12 +14,12 @@ import net.runelite.client.plugins.microbot.mixology.enums.PotionModifier; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.time.Duration; import java.time.Instant; @@ -98,7 +97,7 @@ public boolean run(MixologyConfig config) { if (digweed != null && !Rs2Player.isAnimating() && !Rs2Inventory.hasItem(DIGWEED) && config.pickDigWeed()) { - Rs2GameObject.interact(digweed.coordinate()); + Microbot.getRs2TileObjectCache().query().withId(digweed.objectId()).interact(); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); return; @@ -165,7 +164,7 @@ public boolean run(MixologyConfig config) { } if (Rs2Inventory.hasItem(config.agaHerb().toString()) || Rs2Inventory.hasItem(config.lyeHerb().toString()) || Rs2Inventory.hasItem(config.moxHerb().toString())) { - Rs2GameObject.interact(ObjectID.MM_LAB_MILL); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MILL).interact(); Rs2Player.waitForAnimation(); sleepGaussian(450, 150); if (!config.useQuickActionRefiner()) { @@ -206,7 +205,7 @@ public boolean run(MixologyConfig config) { } break; case DEPOSIT_HOPPER: - if (Rs2GameObject.interact(ObjectID.MM_LAB_HOPPER)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_HOPPER).interact()) { Rs2Player.waitForWalking(); Rs2Inventory.waitForInventoryChanges(10000); mixologyState = MixologyState.MIX_POTION_STAGE_1; @@ -252,7 +251,7 @@ public boolean run(MixologyConfig config) { } break; case TAKE_FROM_MIXIN_VESSEL: - Rs2GameObject.interact(MIXING_VESSEL.objectId()); + Microbot.getRs2TileObjectCache().query().withId(MIXING_VESSEL.objectId()).interact(); boolean result = Rs2Inventory.waitForInventoryChanges(5000); if (result) { mixologyState = MixologyState.MIX_POTION_STAGE_1; @@ -305,7 +304,7 @@ public boolean run(MixologyConfig config) { mixologyState = MixologyState.MIX_POTION_STAGE_1; return; } - if (Rs2GameObject.interact(AlchemyObject.CONVEYOR_BELT.objectId())) { + if (Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.CONVEYOR_BELT.objectId()).interact()) { Rs2Inventory.waitForInventoryChanges(5000); currentAgaPoints = getAgaPoints(); currentLyePoints = getLyePoints(); @@ -357,25 +356,22 @@ private boolean hasAllFulFilledItems() { private static void processPotion(PotionOrder nonFulfilledPotion) { switch (nonFulfilledPotion.potionModifier()) { case HOMOGENOUS: - GameObject agitator = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.AGITATOR.objectId()); - if (agitator != null && (((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11633 || ((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11632)) { - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.AGITATOR, 11633, 11632)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGITATOR.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.AGITATOR.objectId()); } break; case CONCENTRATED: - GameObject retort = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.RETORT.objectId()); - if (retort != null && (((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11643 || ((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11642)) { - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.RETORT, 11643, 11642)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.RETORT.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.RETORT.objectId()); } break; case CRYSTALISED: - GameObject alembic = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.ALEMBIC.objectId()); - if (alembic != null && (((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11638 || ((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11637)) { - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.ALEMBIC, 11638, 11637)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.ALEMBIC.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.ALEMBIC.objectId()); } @@ -383,16 +379,38 @@ private static void processPotion(PotionOrder nonFulfilledPotion) { } } + private static boolean isAlchemyObjectAnimating(AlchemyObject alchemyObject, int... animationIds) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query().withId(alchemyObject.objectId()).nearest(); + if (model == null) return false; + try { + net.runelite.api.Scene scene = Microbot.getClient().getTopLevelWorldView().getScene(); + int plane = model.getWorldLocation().getPlane(); + int sceneX = model.getLocalLocation().getSceneX(); + int sceneY = model.getLocalLocation().getSceneY(); + net.runelite.api.Tile tile = scene.getTiles()[plane][sceneX][sceneY]; + if (tile == null || tile.getGameObjects() == null) return false; + for (net.runelite.api.GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == alchemyObject.objectId() && go.getRenderable() instanceof DynamicObject) { + int animId = ((DynamicObject) go.getRenderable()).getAnimation().getId(); + for (int id : animationIds) { + if (animId == id) return true; + } + } + } + } catch (Exception ignored) {} + return false; + } + private static void quickActionProcessPotion(PotionOrder nonFulfilledPotion) { switch (nonFulfilledPotion.potionModifier()) { case HOMOGENOUS: - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGITATOR.objectId()).interact(); break; case CONCENTRATED: - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.RETORT.objectId()).interact(); break; case CRYSTALISED: - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.ALEMBIC.objectId()).interact(); break; } } @@ -401,11 +419,11 @@ private void createPotion(PotionOrder potionOrder, MixologyConfig config) { for (PotionComponent component : potionOrder.potionType().components()) { if (canCreatePotion(potionOrder)) break; if (component.character() == 'A') { - Rs2GameObject.interact(AlchemyObject.AGA_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGA_LEVER.objectId()).interact(); } else if (component.character() == 'L') { - Rs2GameObject.interact(AlchemyObject.LYE_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.LYE_LEVER.objectId()).interact(); } else if (component.character() == 'M') { - Rs2GameObject.interact(AlchemyObject.MOX_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.MOX_LEVER.objectId()).interact(); } if (config.useQuickActionLever()) { Rs2Player.waitForAnimation(); @@ -419,23 +437,34 @@ private void createPotion(PotionOrder potionOrder, MixologyConfig config) { } private boolean canCreatePotion(PotionOrder potionOrder) { - // Get the mixer game objects - GameObject[] mixers = { - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_03), // mixer3 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_02), // mixer2 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_01) // mixer1 + Rs2TileObjectModel[] mixerModels = { + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_03).nearest(), + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_02).nearest(), + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_01).nearest() }; - // Check if any mixers are missing - if (Arrays.stream(mixers).anyMatch(Objects::isNull)) { + if (Arrays.stream(mixerModels).anyMatch(Objects::isNull)) { return false; } - // Get animations in correct order - int[] currentAnimations = Arrays.stream(mixers) - .map(mixer -> ((DynamicObject) mixer.getRenderable()).getAnimation().getId()) - .mapToInt(Integer::intValue) - .toArray(); + int[] currentAnimations = new int[3]; + for (int i = 0; i < mixerModels.length; i++) { + Rs2TileObjectModel m = mixerModels[i]; + int animId = -1; + try { + net.runelite.api.Scene scene = Microbot.getClient().getTopLevelWorldView().getScene(); + net.runelite.api.Tile tile = scene.getTiles()[m.getWorldLocation().getPlane()][m.getLocalLocation().getSceneX()][m.getLocalLocation().getSceneY()]; + if (tile != null && tile.getGameObjects() != null) { + for (net.runelite.api.GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == mixerModels[i].getId() && go.getRenderable() instanceof DynamicObject) { + animId = ((DynamicObject) go.getRenderable()).getAnimation().getId(); + break; + } + } + } + } catch (Exception ignored) {} + currentAnimations[i] = animId; + } // Map components to their valid animations Map componentAnimations = Map.of( diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java index 81c9d41588..a6cb592fe5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java @@ -103,7 +103,7 @@ default boolean relightBrazier() { @ConfigItem( keyName = "FletchRoots", - name = "🔪 Fletch Roots to Kindling", + name = "➳ Fletch Roots to Kindling", description = "Convert bruma roots to kindling for fletching XP and more points", position = 2, section = generalSection @@ -114,7 +114,7 @@ default boolean fletchRoots() { @ConfigItem( keyName = "FixBrazier", - name = "🔧 Fix Broken Braziers", + name = "⚒️ Fix Broken Braziers", description = "Repair broken braziers with hammer", position = 3, section = generalSection @@ -123,6 +123,17 @@ default boolean fixBrazier() { return true; } + @ConfigItem( + keyName = "DodgeSnowfall", + name = "❄️ Dodge Snowfall", + description = "Step out of incoming snowfall AoE damage. Most players don't bother — eating food/potions is usually more efficient than interrupting your action to step.", + position = 4, + section = generalSection + ) + default boolean dodgeSnowfall() { + return false; + } + // ==================== HEALING METHOD ==================== @ConfigItem( keyName = "HealingMethod", diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java index a02030527b..cbf922ff65 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java @@ -42,7 +42,7 @@ ) @Slf4j public class MKE_WintertodtPlugin extends Plugin { - static final String version = "2.1.2"; + static final String version = "2.2.1"; // Core plugin components @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index e78459a741..3be48ad8de 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -16,10 +16,10 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -140,7 +140,7 @@ public class MKE_WintertodtScript extends Script { private boolean spamClickingActive = false; private long spamClickStartTime = 0; private long spamClickEndTime = 0; - private GameObject spamClickTarget = null; + private Rs2TileObjectModel spamClickTarget = null; private int spamClicksPerformed = 0; private long lastSpamClick = 0; @@ -151,6 +151,11 @@ public class MKE_WintertodtScript extends Script { // Flag to prioritize brazier lighting at round start private static boolean shouldPriorizeBrazierAtStart = false; + // One-shot: walk to the snowfall-safe fletch tile only on a fresh entry to + // FLETCH_LOGS (i.e. coming from chopping). Resumed fletches after a brazier + // fix/light or an eat tick don't count — we fletch in place. + private static boolean needFletchTileWalk = false; + // For overlay public static double historicalEstimateSecondsLeft = 0; @@ -246,9 +251,9 @@ private static class GameState { boolean wintertodtRespawning; boolean isWintertodtAlive; int playerWarmth; - GameObject brazier; - GameObject brokenBrazier; - GameObject burningBrazier; + Rs2TileObjectModel brazier; + Rs2TileObjectModel brokenBrazier; + Rs2TileObjectModel burningBrazier; boolean needBanking; boolean needPotions = false; // For rejuvenation potion logic int wintertodtHp = -1; @@ -320,15 +325,13 @@ private void executeStateLogic(GameState gameState) { /* Returns the closest Bruma root that is on the same side as the selected brazier (<= 8 tiles from that brazier). */ - private GameObject getOwnSideRoot() + private Rs2TileObjectModel getOwnSideRoot() { WorldPoint ref = config.brazierLocation().getBRAZIER_LOCATION(); - return Rs2GameObject.getGameObjects(10).stream() - .filter(o -> o.getId() == ObjectID.BRUMA_ROOTS) - .filter(o -> o.getWorldLocation().distanceTo(ref) <= 10) - .min(java.util.Comparator.comparingInt(o -> o.getWorldLocation() - .distanceTo(ref))) - .orElse(null); + return Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.BRUMA_ROOTS) + .within(ref, 10) + .nearest(); } // --------------------------------------------------------------- @@ -908,6 +911,11 @@ private static void changeState(State newState, boolean lock) { if (state == State.FLETCH_LOGS) lastFletchingXpDropTime = 0; if (state == State.BURN_LOGS) lastFiremakingXpDropTime = 0; + // Fresh entry to FLETCH_LOGS → arm the one-shot walk to the safe tile. + if (newState == State.FLETCH_LOGS) { + needFletchTileWalk = true; + } + System.out.println(String.format("[%d] State transition: %s -> %s %s", System.currentTimeMillis(), state, newState, lock ? "(LOCKED)" : "")); @@ -1264,7 +1272,9 @@ private void configureAntibanSettings() { Microbot.log("Activity: " + Rs2Antiban.getActivity().getMethod()); Microbot.log("Play Style: " + Rs2Antiban.getPlayStyle().getName()); Microbot.log("Micro Breaks: " + (Rs2AntibanSettings.takeMicroBreaks ? "Enabled" : "Disabled")); - Microbot.log("Action Cooldown: " + (Rs2AntibanSettings.usePlayStyle ? "Enabled" : "Disabled")); + Microbot.log("Action Cooldown: " + (Rs2AntibanSettings.usePlayStyle + ? "Enabled (chance " + Rs2AntibanSettings.actionCooldownChance + ")" + : "Disabled")); Microbot.log("Mouse Randomization: " + (Rs2AntibanSettings.moveMouseRandomly ? "Enabled" : "Disabled")); Microbot.log("============================"); @@ -1461,13 +1471,22 @@ private GameState analyzeGameState() { gameState.playerWarmth = getWarmthLevel(); - // Object detection - gameState.brazier = Rs2GameObject.findObject(BRAZIER_29312, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - gameState.brokenBrazier = Rs2GameObject.findObject(BRAZIER_29313, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - gameState.burningBrazier = Rs2GameObject.findObject(BURNING_BRAZIER_29314, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); + // Object detection. The previous filter required the brazier object's + // worldLocation to *exactly equal* a hardcoded constant in the Brazier + // enum, but the actual SW-corner of the brazier object differs by a + // tile from that constant (verified live: SE brazier at (1638,3997) + // while OBJECT_BRAZIER_LOCATION is (1639,3998)). Result: every brazier + // slot was permanently null → no light, no relight, no fix, NPE on + // feed. Switch to a tolerant within-radius filter anchored on the + // player-stand tile (BRAZIER_LOCATION). The two sides are 17 tiles + // apart, so a radius of 3 unambiguously selects the chosen side. + WorldPoint brazierAnchor = config.brazierLocation().getBRAZIER_LOCATION(); + gameState.brazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29312) + .within(brazierAnchor, 3).nearest(); + gameState.brokenBrazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29313) + .within(brazierAnchor, 3).nearest(); + gameState.burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314) + .within(brazierAnchor, 3).nearest(); // Health and food management - determine healing strategy if (!autoAdjustedPotionUsage) { @@ -1601,7 +1620,7 @@ private void performHumanLikeBehaviors() { if (random.nextInt(100) < 15) { // 15% chance // Simulate checking other areas occasionally if (currentBrazier != null && random.nextBoolean()) { - Rs2GameObject.hoverOverObject(currentBrazier); + if (currentBrazier != null) Rs2GameObject.hoverOverObject(currentBrazier); } lastMouseMovement = currentTime; } @@ -1817,15 +1836,25 @@ private void performMaintenanceTasks(GameState gameState) { return; } + // Brazier maintenance is THE priority. When snowfall breaks the + // brazier, the fix window closes fast (other players race to repair), + // and an unlit brazier means zero points until it's relit. Both of + // those beat eating — we'll eat next tick once the brazier is back. + if (handleBrazierMaintenance(gameState)) { + return; + } + // Drop unnecessary items dropUnnecessaryItems(); - // Dodge falling snow/damage - dodgeSnowfallDamage(gameState); - - // Handle eating + // Eat — survival. Runs only after brazier is fixed/lit (handled above). handleEating(gameState); + // Dodge falling snow/damage (opt-in; most players just rely on food/potions) + if (config.dodgeSnowfall()) { + dodgeSnowfallDamage(gameState); + } + // Periodic camera check (every 2 minutes during normal operation) if (System.currentTimeMillis() - lastCameraMovement > 120000) { fixCameraPitchIfNeeded(); @@ -1998,10 +2027,12 @@ private void handleMainGameLoop(GameState gameState) if (shouldLightBrazier(gameState)) { Microbot.log("Prioritizing brazier lighting at round start (state: " + state + ")"); return; // Light the brazier first, then resume normal flow next tick - } else { - // Brazier is already lit or doesn't need lighting, reset the flag + } else if (gameState.burningBrazier != null) { + // Only consume the priority when a brazier is *definitively* burning + // (someone else lit it). If both burning- and unlit- slots are null, + // the cache may just be lagging — keep the flag and retry next tick. shouldPriorizeBrazierAtStart = false; - Microbot.log("Brazier lighting priority completed or not needed (state: " + state + ")"); + Microbot.log("Brazier lighting priority completed - already burning (state: " + state + ")"); } } @@ -2215,7 +2246,7 @@ private void handleLightBrazierState(GameState gameState) { } if (gameState.brazier != null) { - if (Rs2GameObject.interact(gameState.brazier, "light")) { + if (gameState.brazier.click("light")) { Microbot.log("Lighting brazier"); // Reset priority flag after successful lighting attempt @@ -2289,8 +2320,8 @@ private void handleChopRootsState(GameState gameState) if (!Rs2Player.isAnimating()) { - GameObject root = getOwnSideRoot(); - if (root != null && Rs2GameObject.interact(root, "Chop")) + Rs2TileObjectModel root = getOwnSideRoot(); + if (root != null && root.click("Chop")) { sleepUntilTrue(Rs2Player::isAnimating, 100, 3000); maybeNudgeMouse(); @@ -2323,18 +2354,15 @@ private void handleFletchLogsState(GameState gameState) { /* ---------- PRIORITY BLOCK 1: FIX BROKEN BRAZIER FIRST ----------- */ if (gameState.brokenBrazier != null && config.fixBrazier()) { - - sleepGaussian(200, 150); - // Stop fletching temporarily to fix brazier if (fletchingState.isActive()) { fletchingState.stopFletching(FletchingInterruptType.BRAZIER_BROKEN); } - + // Deselect any items before fixing deselectSelectedItem(); - - Rs2GameObject.interact(gameState.brokenBrazier, "fix"); + + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier (priority during fletching)"); resetActions = true; actionsPerformed++; @@ -2343,20 +2371,17 @@ private void handleFletchLogsState(GameState gameState) { /* ----------------------------------------------------------------- */ /* ---------- PRIORITY BLOCK 2: RELIGHT BRAZIER SECOND ------------ */ - if (gameState.burningBrazier == null && gameState.brazier != null && + if (gameState.burningBrazier == null && gameState.brazier != null && config.relightBrazier() && gameState.isWintertodtAlive) { - - sleepGaussian(200, 150); - // Stop fletching temporarily to relight brazier if (fletchingState.isActive()) { fletchingState.stopFletching(FletchingInterruptType.BRAZIER_WENT_OUT); } - + // Deselect any items before relighting deselectSelectedItem(); - - Rs2GameObject.interact(gameState.brazier, "light"); + + gameState.brazier.click("light"); Microbot.log("Relighting brazier (priority during fletching)"); resetActions = true; actionsPerformed++; @@ -2400,12 +2425,23 @@ private void handleFletchLogsState(GameState gameState) { } /* ---------- start / continue fletching ------------------- */ - if (!isCurrentlyFletching() && gameState.burningBrazier != null) { + // Fletching only needs knife + roots; a burning brazier nearby isn't a + // precondition. Gating on burningBrazier caused silent idles when the + // brazier went out and the relight priority block didn't fire (e.g. the + // brazier object briefly fell out of the cache). + if (!isCurrentlyFletching()) { sleepGaussian(250, 150); if (random.nextInt(100) < 10) { sleepGaussian(400, 600); } - navigateToBrazier(); + // Walk to the snowfall-safe fletch tile only on the first + // fletch of this state entry (i.e. fresh from chopping). + // Resumed fletches after a brazier fix/light/eat happen in + // place — no point trekking back for a partial pile. + if (needFletchTileWalk) { + navigateToFletchSpot(); + needFletchTileWalk = false; + } // Keep knife in slot-27 optimisation Rs2ItemModel knife = Rs2Inventory.get(WintertodtInventoryManager.knifeToUse); @@ -2454,7 +2490,7 @@ private void handleFletchLogsState(GameState gameState) { } /* Pre-select knife and hover when we have many roots */ - if (rootCount > knifePreselectThreshold && !isKnifeSelected() && gameState.burningBrazier != null) { + if (rootCount > knifePreselectThreshold && !isKnifeSelected()) { Rs2Inventory.interact(WintertodtInventoryManager.knifeToUse, "Use"); sleepGaussian(120, 40); @@ -2494,8 +2530,8 @@ private void handleBurnLogsState(GameState gameState) { if (feedingState.isActive()) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_BROKEN); } - - Rs2GameObject.interact(gameState.brokenBrazier, "fix"); + + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier"); resetActions = true; actionsPerformed++; @@ -2504,18 +2540,15 @@ private void handleBurnLogsState(GameState gameState) { /* ----------------------------------------------------------------- */ /* ---------- PRIORITY BLOCK 2: RELIGHT BRAZIER SECOND ------------ */ - TileObject burningBrazier = gameState.burningBrazier; // side-specific - if (burningBrazier == null && gameState.brazier != null && + Rs2TileObjectModel burningBrazier = gameState.burningBrazier; // side-specific + if (burningBrazier == null && gameState.brazier != null && config.relightBrazier() && gameState.isWintertodtAlive) { - - sleepGaussian(200, 150); - // Stop feeding temporarily to relight brazier if (feedingState.isActive()) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_WENT_OUT); } - - Rs2GameObject.interact(gameState.brazier, "light"); + + gameState.brazier.click("light"); Microbot.log("Relighting brazier"); resetActions = true; actionsPerformed++; @@ -2534,15 +2567,16 @@ private void handleBurnLogsState(GameState gameState) { } /* ---------- start / continue feeding ------------------- */ - if (!isCurrentlyFeeding() && - gameState.hasItemsToBurn) { + if (!isCurrentlyFeeding() && + gameState.hasItemsToBurn && + burningBrazier != null) { sleepGaussian(200, 150); if (random.nextInt(100) < 10) { sleepGaussian(400, 600); } - - if (Rs2GameObject.interact(burningBrazier, "feed")) { + + if (burningBrazier.click("feed")) { feedingState.startFeeding(); // Initialize animation tracking for new feeding session lastFeedingAnimationTime = System.currentTimeMillis(); @@ -2550,6 +2584,10 @@ private void handleBurnLogsState(GameState gameState) { actionsPerformed++; Microbot.log("Started feeding brazier"); maybeNudgeMouse(); + + if (Rs2AntibanSettings.usePlayStyle) { + Rs2Antiban.actionCooldown(); + } } } @@ -2649,16 +2687,11 @@ else if (Rs2Inventory.hasItem(ItemID.BRUMA_KINDLING)) { } // Find and interact with crate - TileObject crate = Rs2GameObject.findObject(CRATE_OBJECT_ID, CRATE_LOCATION); - if (crate == null) { - crate = Rs2GameObject.getGameObjects(CRATE_OBJECT_ID).stream() - .filter(c -> c.getWorldLocation().distanceTo(CRATE_LOCATION) <= 2) - .findFirst() - .orElse(null); - } - + Rs2TileObjectModel crate = Microbot.getRs2TileObjectCache().query().withId(CRATE_OBJECT_ID) + .where(o -> o.getWorldLocation().distanceTo(CRATE_LOCATION) <= 2).nearest(); + if (crate != null) { - if (Rs2GameObject.interact(crate, "Take-concoction")) { + if (crate.click("Take-concoction")) { // Wait for inventory change int beforeCount = Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF); sleepUntilTrue(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) > beforeCount, 100, 3000); @@ -2755,17 +2788,12 @@ else if (Rs2Inventory.hasItem(ItemID.BRUMA_KINDLING)) { } // Find sprouting roots and pick - TileObject roots = Rs2GameObject.findObject(SPROUTING_ROOTS_OBJECT_ID, SPROUTING_ROOTS); - if (roots == null) { - roots = Rs2GameObject.getGameObjects(SPROUTING_ROOTS_OBJECT_ID).stream() - .filter(r -> r.getWorldLocation().distanceTo(SPROUTING_ROOTS) <= 2) - .findFirst() - .orElse(null); - } - + Rs2TileObjectModel roots = Microbot.getRs2TileObjectCache().query().withId(SPROUTING_ROOTS_OBJECT_ID) + .where(r -> r.getWorldLocation().distanceTo(SPROUTING_ROOTS) <= 2).nearest(); + if (roots != null) { Microbot.log("Picking herbs (need " + herbsNeeded + " more)"); - if (Rs2GameObject.interact(roots, "Pick")) { + if (roots.click("Pick")) { sleepUntilTrue(() -> Rs2Player.isAnimating(), 100, 2000); actionsPerformed++; } else { @@ -2904,7 +2932,8 @@ private boolean useBreadmaNpcForPotions(int concoctions, int herbs) { } // Find Brew'ma NPC - Rs2NpcModel brewmaNpc = Rs2Npc.getNpcInLineOfSight("Brew'ma"); + Rs2NpcModel brewmaNpc = Microbot.getRs2NpcCache().query().withName("Brew'ma") + .where(n -> n.hasLineOfSight()).nearestOnClientThread(); if (brewmaNpc == null) { Microbot.log("Could not find Brew'ma NPC - walking closer"); Rs2Walker.walkFastCanvas(BREWMA_NPC_INTERACT_LOCATION); @@ -2935,7 +2964,7 @@ private boolean useBreadmaNpcForPotions(int concoctions, int herbs) { Microbot.log("Selected herb from inventory"); // Then click on the Brew'ma NPC while herb is selected - if (Rs2Npc.interact(brewmaNpc, "Use")) { + if (brewmaNpc.click("Use")) { Microbot.log("Used herb on Brew'ma NPC - waiting for conversion"); // Wait for the conversion to complete (should be instant according to user) @@ -3103,6 +3132,59 @@ private boolean shouldBurnLogs(GameState gameState) { * @param gameState Current game state * @return true if food was consumed */ + /** + * Top-of-loop brazier priority handler: fix a broken brazier, then relight + * an unlit one. Runs before {@link #handleEating} so a snowfall-induced + * break doesn't lose us the repair window to another player. Returns true + * if a click was issued (caller should skip the rest of the tick). + */ + private boolean handleBrazierMaintenance(GameState gameState) { + // Without this guard the 60ms loop would re-issue the same click every + // tick while the repair/light animation runs (~30 clicks in 2s) — an + // obvious bot signature. resetActions is cleared once the next action + // begins, so a single repair click can trigger. + if (resetActions) return false; + + // Skip while chopping — the roots are several tiles from the brazier, + // so by the time we abandon the chop and walk over, another player + // has already repaired/relit. The interruption costs us a chop cycle + // for nothing. + if (state == State.CHOP_ROOTS) return false; + + if (gameState.brokenBrazier != null && config.fixBrazier()) { + if (fletchingState.isActive()) { + fletchingState.stopFletching(FletchingInterruptType.BRAZIER_BROKEN); + } + if (feedingState.isActive()) { + feedingState.stopFeeding(FeedingInterruptType.BRAZIER_BROKEN); + } + deselectSelectedItem(); + gameState.brokenBrazier.click("fix"); + Microbot.log("Fixing broken brazier (priority)"); + resetActions = true; + actionsPerformed++; + return true; + } + + if (gameState.burningBrazier == null && gameState.brazier != null + && config.relightBrazier() && gameState.isWintertodtAlive) { + if (fletchingState.isActive()) { + fletchingState.stopFletching(FletchingInterruptType.BRAZIER_WENT_OUT); + } + if (feedingState.isActive()) { + feedingState.stopFeeding(FeedingInterruptType.BRAZIER_WENT_OUT); + } + deselectSelectedItem(); + gameState.brazier.click("light"); + Microbot.log("Relighting brazier (priority)"); + resetActions = true; + actionsPerformed++; + return true; + } + + return false; + } + private boolean handleEating(GameState gameState) { if (gameState.playerWarmth <= config.eatAtWarmthLevel()) { try { @@ -3271,10 +3353,14 @@ private boolean shouldLightBrazier(GameState gameState) { if (gameState.brazier == null || gameState.burningBrazier != null) { setLockState(State.LIGHT_BRAZIER, false); - // Reset priority flag if brazier is already lit or doesn't exist - if (shouldPriorizeBrazierAtStart) { + // Only consume the round-start priority when a brazier is *definitively* + // burning. If both are null, the cache hasn't seen the brazier yet + // (common right after the round-start tick) — keep the flag so we'll + // retry once detection catches up, instead of giving up and chopping + // for the rest of the round with an unlit brazier. + if (shouldPriorizeBrazierAtStart && gameState.burningBrazier != null) { shouldPriorizeBrazierAtStart = false; - Microbot.log("Brazier priority reset - brazier already lit or unavailable"); + Microbot.log("Brazier priority reset - brazier already lit"); } return false; } @@ -3339,6 +3425,34 @@ private void navigateToBrazier() { } } + /** + * Navigates to the snowfall-safe fletch tile (one tile south of the + * brazier-stand tile). Used only while fletching so we don't tank + * snowfall AoE for the entire root pile. + */ + private void navigateToFletchSpot() { + try { + WorldPoint fletchLocation = config.brazierLocation().getFLETCH_LOCATION(); + double distance = Rs2Player.getWorldLocation().distanceTo(fletchLocation); + + if (!BreakHandlerScript.isLockState()) { + BreakHandlerScript.setLockState(true); + Microbot.log("Locking break handler"); + } + + if (distance > 8) { + Rs2Walker.walkTo(fletchLocation, 1); + Rs2Player.waitForWalking(); + } else if (distance >= 1) { + Rs2Walker.walkFastCanvas(fletchLocation); + sleepGaussian(400, 100); + } + + } catch (Exception e) { + System.err.println("Error navigating to fletch spot: " + e.getMessage()); + } + } + /** * Dodges snowfall damage by tracking specific projectiles. */ @@ -3380,24 +3494,15 @@ private void dodgeSnowfallDamage(GameState gameState) { Rs2Player.waitForWalking(1500); resetActions = true; Microbot.log("Dodged snowfall damage (80% chance triggered)"); - Microbot.log("Waiting for burning brazier to go out after snowfall damage..."); - Rs2GameObject.hoverOverObject(Rs2GameObject.findReachableObject("brazier", false, 4, Rs2Player.getWorldLocation())); - - boolean brazierWentOut = sleepUntilTrue( - () -> { - // Wait until burning brazier is gone OR we're no longer in burn state - GameObject burningBrazier = Rs2GameObject.getGameObject(BURNING_BRAZIER_29314,5); - return burningBrazier == null || (state != State.BURN_LOGS && state != State.FLETCH_LOGS); - }, - 100, // Check every 100ms - 5000 // Timeout after 5 seconds - ); - - if (brazierWentOut) { - Microbot.log("Brazier state changed, continuing..."); - } else { - Microbot.log("Timeout waiting for brazier change - continuing anyway"); - } + // Don't block here. The previous implementation sleepUntilTrue'd + // for the brazier to go out (timeout 5s, but in practice the + // executor wedged for ~140s — see "avg loop time: 141100ms" in + // the round logs), which starved handleEating and nearly killed + // the player. The dodge already moved us out of the AoE; let + // the next script tick re-analyze: the priority blocks in + // handleFletchLogsState / handleBurnLogsState will detect a + // broken brazier and click "fix" (auto-walks back), or relight + // an unlit brazier — exactly what we want here. } else { Microbot.log("Snowfall detected but purposely NOT dodging (20% chance - staying put for realism ;) )"); } @@ -4169,7 +4274,7 @@ private void updateRoundTimer() { * @param gameState Current game state * @return GameObject to hover over, or null if none appropriate */ - private GameObject getNextInteractiveObject(GameState gameState) { + private Rs2TileObjectModel getNextInteractiveObject(GameState gameState) { try { // Priority 1: Broken brazier (if we can fix it) if (gameState.brokenBrazier != null && config.fixBrazier()) { @@ -4191,7 +4296,7 @@ private GameObject getNextInteractiveObject(GameState gameState) { // Priority 4: Bruma roots on our side (if we intend to chop) if (!gameState.inventoryFull && !gameState.hasItemsToBurn && !gameState.needBanking && !gameState.needPotions) { - GameObject root = getOwnSideRoot(); + Rs2TileObjectModel root = getOwnSideRoot(); if (root != null) { Microbot.log("DEBUG: Will hover over bruma roots for chopping"); return root; @@ -4257,9 +4362,9 @@ private void handleRoundTimerMouseBehavior(GameState gameState) { // Start hovering when time remaining reaches our calculated hover time (only if not spam clicking) if (!hoveredForNextRound && !spamClickingActive && timeUntilStart > 0 && timeUntilStart <= hoverBeforeStartTime) { - GameObject nextObject = getNextInteractiveObject(gameState); + Rs2TileObjectModel nextObject = getNextInteractiveObject(gameState); if (nextObject != null) { - Rs2GameObject.hoverOverObject(nextObject); + if (nextObject.getClickbox() != null) moveCursorHumanized(nextObject.getClickbox().getBounds()); hoveredForNextRound = true; Microbot.log("Hovering over next interactive object: " + nextObject.getId() + " (" + (timeUntilStart / 1000.0) + "s before round start)"); @@ -4479,7 +4584,7 @@ private void maybeNudgeMouse() dx = Math.max(-80, Math.min(80, dx)); dy = Math.max(-80, Math.min(80, dy)); - Microbot.getMouse().move(start.x + dx, start.y + dy); + moveCursorHumanized(start.x + dx, start.y + dy); /* 3. ~50 % chance of a quick follow-up wobble */ if (random.nextBoolean()) @@ -4491,13 +4596,38 @@ private void maybeNudgeMouse() dx2 = Math.max(-30, Math.min(30, dx2)); dy2 = Math.max(-30, Math.min(30, dy2)); - Microbot.getMouse().move(start.x + dx + dx2, - start.y + dy + dy2); + moveCursorHumanized(start.x + dx + dx2, + start.y + dy + dy2); } } catch (Exception ignored) {} } + /** + * Routes cursor movement through NaturalMouse when available. Direct + * Microbot.getMouse().move(x, y) dispatches a teleport MOUSE_MOVED event + * with no path — visually obvious and trivially bot-detectable. Whenever + * we want to *move* the cursor (hover, nudge, pre-position before a spam + * click), use this so the move follows a smooth Bezier-style curve. + */ + private static void moveCursorHumanized(int x, int y) + { + if (x <= 1 || y <= 1) return; + if (Microbot.naturalMouse != null) { + Microbot.naturalMouse.moveTo(x, y); + } else { + Microbot.getMouse().move(x, y); + } + } + + private static void moveCursorHumanized(java.awt.Rectangle rect) + { + if (rect == null) return; + int cx = (int) (rect.getX() + rect.getWidth() / 2.0); + int cy = (int) (rect.getY() + rect.getHeight() / 2.0); + moveCursorHumanized(cx, cy); + } + /* ------------ knife selection helpers ------------------------------ */ private boolean isKnifeSelected() { @@ -4760,7 +4890,7 @@ private void executeSpamClicking(GameState gameState) { /** * Determines the best target for spam clicking (usually the brazier we'll interact with). */ - private GameObject determineSpamClickTarget(GameState gameState) { + private Rs2TileObjectModel determineSpamClickTarget(GameState gameState) { // Priority 1: Unlit brazier (what we'll light when round starts) if (gameState.brazier != null && gameState.burningBrazier == null) { return gameState.brazier; @@ -4789,7 +4919,7 @@ private void performSpamClick() { } // Just hover and click without actually interacting - Rs2GameObject.hoverOverObject(spamClickTarget); + if (spamClickTarget.getClickbox() != null) moveCursorHumanized(spamClickTarget.getClickbox().getBounds()); // Small delay between hover and click for realism sleepGaussian(60, 40); @@ -5211,7 +5341,8 @@ private boolean interactWithRewardCart() { lastRewardCartInteraction = System.currentTimeMillis(); // Try to interact with reward cart by searching for the "Reward" text on the object - if (Rs2GameObject.interact("Reward", "Big-search", false)) { + var rewardCart = Microbot.getRs2TileObjectCache().query().withName("Reward").nearestOnClientThread(); + if (rewardCart != null && rewardCart.click("Big-search")) { Microbot.status = "Interacting with reward cart"; return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java index 8a1322847f..41a12c903e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java @@ -6,11 +6,15 @@ @AllArgsConstructor public enum Brazier { - SOUTH_EAST(new WorldPoint(1638, 3996, 0), new WorldPoint(1639, 3998, 0)), - SOUTH_WEST(new WorldPoint(1621, 3996, 0), new WorldPoint(1621, 3998, 0)); + SOUTH_EAST(new WorldPoint(1638, 3996, 0), new WorldPoint(1639, 3998, 0), new WorldPoint(1638, 3995, 0)), + SOUTH_WEST(new WorldPoint(1621, 3996, 0), new WorldPoint(1621, 3998, 0), new WorldPoint(1621, 3995, 0)); @Getter public final WorldPoint BRAZIER_LOCATION; @Getter public final WorldPoint OBJECT_BRAZIER_LOCATION; + // One tile south of the brazier-stand tile — the same offset the dodge logic uses. + // Snowfall AoE around the brazier doesn't reach here, so it's safe to fletch on. + @Getter + public final WorldPoint FLETCH_LOCATION; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java index ca7c2e930f..38831bb7ce 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java @@ -8,7 +8,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -133,12 +133,12 @@ public static boolean attemptLeaveWintertodt() } /* Step 3: Click the door to trigger the dialog */ - GameObject exitDoor = Rs2GameObject.getGameObject(ObjectID.DOORS_OF_DINH, 6); + Rs2TileObjectModel exitDoor = Microbot.getRs2TileObjectCache().query().withId(ObjectID.DOORS_OF_DINH).within(6).nearest(); if (exitDoor != null) { Microbot.log("Clicking Wintertodt exit door..."); - if (Rs2GameObject.interact(exitDoor, "Leave") - || Rs2GameObject.interact(exitDoor, "Exit") - || Rs2GameObject.interact(exitDoor, "Open")) { + if (exitDoor.click("Leave") + || exitDoor.click("Exit") + || exitDoor.click("Open")) { sleepGaussian(800, 200); // Wait for dialog to appear return false; // Continue next tick to handle dialog } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java index 8eed3c4e6e..cfc49c76b5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class MmCavesPlugin extends Plugin { - public static String version = "1.0.1"; + public static String version = "1.0.2"; @Inject private MmCavesConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java index 827d5e8715..192c8eba24 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java @@ -1,26 +1,23 @@ package net.runelite.client.plugins.microbot.mmcaves; import com.google.common.collect.Table; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.GroundObject; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.grounditems.GroundItem; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.mmcaves.enums.CombatStyle; import net.runelite.client.plugins.microbot.mmcaves.enums.LightSources; import net.runelite.client.plugins.microbot.mmcaves.enums.Mode; import net.runelite.client.plugins.microbot.mmcaves.enums.State; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.magic.Runes; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -35,7 +32,6 @@ import java.util.Arrays; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.TimeUnit; public class MmCavesScript extends Script { @@ -224,9 +220,9 @@ private void handleWalkToStart() { private void handleCheckEmptyCave() { Microbot.log("Checking if cave is empty"); - GroundObject hole = Rs2GameObject.getGroundObject(28772); + Rs2TileObjectModel hole = Microbot.getRs2TileObjectCache().query().withId(28772).nearest(); if (hole != null) { - Rs2GameObject.interact(hole.getWorldLocation(), "Look-in"); + hole.click("Look-in"); sleepUntil(() -> caveIsEmpty, 3000); } } @@ -251,11 +247,11 @@ private void handleWorldHop() { private void handleEnterCave() { Microbot.log("Entering cave..."); - GroundObject hole = Rs2GameObject.getGroundObject(28772); + Rs2TileObjectModel hole = Microbot.getRs2TileObjectCache().query().withId(28772).nearest(); if (hole != null) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); - Rs2GameObject.interact(hole.getWorldLocation(), "Enter"); + hole.click("Enter"); sleepUntil(() -> caveIsEmpty, 3000); } } @@ -304,11 +300,11 @@ private void handleFight() { // This is not efficient and needs improvement but sufficient for first release // If not it impacts magic a lot due to longer delay - Optional monkey = Rs2Npc.getNpcs("Maniacal monkey") - .filter(npc -> npc.getWorldLocation().equals(new WorldPoint(2451, 9159, 1))) - .filter(npc -> !npc.isDead()) - .findFirst(); - Rs2NpcModel target = monkey.orElse(null); + Rs2NpcModel target = Microbot.getRs2NpcCache().query() + .withName("Maniacal monkey") + .where(npc -> npc.getWorldLocation().equals(new WorldPoint(2451, 9159, 1)) + && !npc.getNpc().isDead()) + .nearestOnClientThread(); boolean attacked = attemptAttack(target); if (attacked) walkBetweenTiles(); @@ -339,9 +335,9 @@ private void stopAndLog() { sleepUntil(() -> plugin.getMyWorldPoint().distanceTo(EXIT_TILE) < 3, 1000); if ( plugin.getMyWorldPoint().distanceTo(EXIT_TILE) < 3) { - GameObject rope = Rs2GameObject.getGameObject(28775); + Rs2TileObjectModel rope = Microbot.getRs2TileObjectCache().query().withId(28775).nearest(); if (rope != null) { - Rs2GameObject.interact(rope, "Climb-up"); + rope.click("Climb-up"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == cavesUpstairs, 5000); } } @@ -433,7 +429,7 @@ private boolean attemptAttack(Rs2NpcModel target) { if (!config.shouldAutoCast()) { attacked = Rs2Magic.castOn(config.magicSpell().getSpell(), target); } else { - attacked = Rs2Npc.interact(target, "Attack"); + attacked = target.click("Attack"); } if (attacked) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java b/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java index 85f5772738..516632dd6e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java @@ -10,7 +10,6 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -236,13 +235,13 @@ private void handleCatching(MoonlightMothConfig config) { WorldArea excludedArea = new WorldArea(1550, 9426, 21, 8, 0); - Rs2Npc.getNpcs(NpcID.MOTH_MOONLIGHT).filter(moth -> { + Microbot.getRs2NpcCache().query().withId(NpcID.MOTH_MOONLIGHT).where(moth -> { WorldPoint location = moth.getWorldLocation(); return location != null && !excludedArea.contains(location); - }).findFirst().ifPresent(moth -> { + }).toList().stream().findFirst().ifPresent(moth -> { if (!Rs2Player.isAnimating() && !Rs2Player.isInteracting()) { var beforeCount = Rs2Inventory.count(ItemID.BUTTERFLY_JAR_MOONMOTH); - if (Rs2Npc.interact(moth, "Catch")) { + if (moth.click("Catch")) { logOnceToChat("Attempting to catch Moonlight Moth at: " + moth.getWorldLocation(), true); Rs2Player.waitForAnimation(2000); var afterCount = Rs2Inventory.count(ItemID.BUTTERFLY_JAR_MOONMOTH); diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java index 9b798212e0..7b791187b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java @@ -35,7 +35,7 @@ ) @Slf4j public class MoonsOfPerilPlugin extends Plugin { - static final String version = "1.0.3"; + static final String version = "1.0.4"; @Inject private MoonsOfPerilConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java index 3c542de17d..0021ce57a5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java @@ -13,9 +13,7 @@ import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilPlugin; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -74,7 +72,7 @@ public State execute() { } int bossNpcID = NpcID.PMOON_BOSS_BLOOD_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -99,7 +97,7 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the jaguar NPC is found. */ public boolean isSpecialAttack1Sequence() { - Rs2NpcModel jaguar = Rs2Npc.getNpc(NpcID.PMOON_BOSS_JAGUAR); + Rs2NpcModel jaguar = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_JAGUAR).nearest(); return (jaguar != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID)); } @@ -107,7 +105,7 @@ public boolean isSpecialAttack1Sequence() { * Returns True if the Moonfire gameobject is found and the boss is not attackable. */ public boolean isSpecialAttack2Sequence() { - return Rs2GameObject.exists(ObjectID.PMOON_BOSS_BLOOD_FIRE) && Rs2Npc.getNpc(sigilNpcID) == null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_BOSS_BLOOD_FIRE).nearest() != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID); } /** Blood Moon – Blood Rain Special Attack Handler */ @@ -119,7 +117,7 @@ public void specialAttack2Sequence() { sleepUntil(() -> Rs2Player.getWorldLocation().equals(afterRainTile)); while (isSpecialAttack2Sequence()) { WorldPoint playerTile = Rs2Player.getWorldLocation(); - GameObject bloodPool = Rs2GameObject.getGameObject(o -> o.getId() == ObjectID.PMOON_BOSS_BLOOD_POOL && o.getWorldLocation().equals(playerTile)); + var bloodPool = Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_BOSS_BLOOD_POOL).where(o -> o.getWorldLocation().equals(playerTile)).nearest(); if (bloodPool != null) { if (debugLogging) {Microbot.log("Standing on dangerous tile: " + playerTile);} WorldPoint safeTile = getRandomSafeTile(ObjectID.PMOON_BOSS_BLOOD_POOL, 1); @@ -143,7 +141,7 @@ public void specialAttack1Sequence() { final long startMs = System.currentTimeMillis(); /* 1 find the sigil NPC (2×2, SW tile = sigilLoc) */ - Rs2NpcModel sigilNpc = Rs2Npc.getNpcs(n -> n.getId() == sigilNpcID).findFirst().orElse(null); + Rs2NpcModel sigilNpc = Microbot.getRs2NpcCache().query().where(n -> n.getId() == sigilNpcID).toList().stream().findFirst().orElse(null); if (sigilNpc == null) { if (debugLogging) {Microbot.log("no sigil NPC – bail");} return; @@ -181,10 +179,10 @@ public void specialAttack1Sequence() { if (!arrived) return; /* 4 ─ lock the target jaguar (SW tile == spawnTile) ---------------- */ - Rs2NpcModel targetJaguar = Rs2Npc.getNpcs(n -> + Rs2NpcModel targetJaguar = Microbot.getRs2NpcCache().query().where(n -> n.getId() == NpcID.PMOON_BOSS_JAGUAR && n.getWorldLocation().equals(spawnTile)) - .findFirst().orElse(null); + .toList().stream().findFirst().orElse(null); if (targetJaguar == null) { if (debugLogging) {Microbot.log("jaguar not on expected spawn");} return; @@ -195,7 +193,7 @@ public void specialAttack1Sequence() { while (isSpecialAttack1Sequence() && System.currentTimeMillis() - startMs < TIMEOUT_MS) { int bloodPoolTick = MoonsOfPerilPlugin.bloodPoolTick; - if (targetJaguar.getAnimation() == 12492) { + if (targetJaguar.getNpc().getAnimation() == 12492) { sleep(600); break; } @@ -204,7 +202,7 @@ public void specialAttack1Sequence() { Rs2Walker.walkFastCanvas(evadeTile, true); } else if (bloodPoolTick == 5) { if (debugLogging) {Microbot.log("ATTACK jaguar");} - Rs2Npc.attack(targetJaguar); + targetJaguar.click("Attack"); } else if (bloodPoolTick == 6) { if (debugLogging) {Microbot.log("Clicking on ground to stop attacking");} @@ -226,10 +224,12 @@ public static WorldPoint getRandomSafeTile(int dangerousId, int distance) if (centre == null) return null; // ── 2. collect all dangerous tiles within radius ── - Set dangerTiles = Rs2GameObject - .getGameObjects(o -> o.getId() == dangerousId, distance) + Set dangerTiles = Microbot.getRs2TileObjectCache().query() + .withId(dangerousId) + .within(distance) + .toList() .stream() - .map(GameObject::getWorldLocation) + .map(o -> o.getWorldLocation()) .collect(Collectors.toSet()); // ── 3. enumerate every tile in the square centred on the player ── diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java index 9488b6f6e2..47ce2a0021 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java @@ -12,8 +12,7 @@ import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -74,7 +73,7 @@ public State execute() { sleepUntil(() -> Rs2Widget.isWidgetVisible(bossHealthBarWidgetID),5_000); } int bossNpcID = NpcID.PMOON_BOSS_BLUE_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -100,7 +99,7 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the tornado NPC is found. */ public boolean isSpecialAttack1Sequence() { - return (Rs2Npc.getNpc(NpcID.PMOON_BOSS_WINTER_STORM) != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) && Rs2Npc.getNpc(sigilNpcID) == null); + return (Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_WINTER_STORM).nearest() != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null); } public void specialAttack1Sequence() @@ -112,7 +111,7 @@ public void specialAttack1Sequence() boss.eatIfNeeded(); boss.drinkIfNeeded(); if (debugLogging) {Microbot.log("Sleeping until the special attack sequence is over");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); } public void specialAttack2IdleSequence() @@ -124,7 +123,7 @@ public void specialAttack2IdleSequence() boss.eatIfNeeded(); boss.drinkIfNeeded(); if (debugLogging) {Microbot.log("Sleeping until the special attack sequence is over");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); } @@ -132,8 +131,8 @@ public void specialAttack2IdleSequence() * Returns True if the icicle NPC is found. */ public boolean isSpecialAttack2Sequence() { - Rs2NpcModel icicle = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ICICLE_UNCRACKED); - Rs2NpcModel sigil = Rs2Npc.getNpc(sigilNpcID); + Rs2NpcModel icicle = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ICICLE_UNCRACKED).nearest(); + Rs2NpcModel sigil = Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest(); if (icicle != null && sigil == null) { if (debugLogging) {Microbot.log("An icicle has spawned – We've entered Special Attack 2 Sequence");} return true; @@ -163,10 +162,10 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) while (isSpecialAttack2Sequence() && matches.isEmpty() && System.currentTimeMillis() - pollStart < POLL_TIMEOUT_MS) { - matches = Rs2Npc.getNpcs(n -> + matches = Microbot.getRs2NpcCache().query().where(n -> n.getId() == ICICLE_NPC_ID && - n.getAnimation() == ICICLE_ANIM_ID) - .collect(Collectors.toList()); + n.getNpc().getAnimation() == ICICLE_ANIM_ID) + .toList(); if (matches.isEmpty()) sleep(300); } @@ -193,7 +192,7 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) System.currentTimeMillis() - phaseStart < PHASE_TIMEOUT_MS) { if (!Rs2Combat.inCombat()) { - Rs2Npc.attack(icicle); + icicle.click("Attack"); } WorldPoint attackTile = Rs2Player.getWorldLocation(); if (debugLogging) {Microbot.log(ts.get() + "Attack location calculated as: " + attackTile);} @@ -222,7 +221,7 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) boss.drinkIfNeeded(); if (debugLogging) {Microbot.log(ts.get() + "Waiting for all icicles to despawn…");} - sleepUntil(() -> Rs2Npc.getNpc(ICICLE_NPC_ID) == null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(ICICLE_NPC_ID).nearest() == null); if (debugLogging) {Microbot.log(ts.get() + "specialAttack2Sequence() COMPLETE");} } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java index d15579c074..90124359df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java @@ -12,11 +12,9 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; @@ -39,19 +37,34 @@ public BossHandler(MoonsOfPerilConfig cfg) { this.debugLogging = cfg.debugLogging(); } - /** Walks to the chosen boss lobby. */ + /** Walks to the chosen boss lobby. + * + * Loops the walker with a reached-distance tolerance. The previous implementation called + * {@code walkWithState(bossWorldPoint, 0)} (exact-tile match) followed by a + * {@code walkFastCanvas(bossWorldPoint)} fallback; when the walker exited early on a single + * pass (e.g. cross-region transport, door handler interrupt) the canvas-click fallback tried + * to click a tile dozens of tiles off-screen, {@code LocalPoint.fromWorld} returned null, + * and the plugin looped forever without the player ever moving. + */ public void walkToBoss(Rs2InventorySetup inventorySetup, String bossName, WorldPoint bossWorldPoint) { if (inventorySetup != null) { equipInventorySetup(inventorySetup); } if (debugLogging) {Microbot.log("Walking to " + bossName + " lobby");} - Rs2Walker.walkWithState(bossWorldPoint, 0); - sleep(600); - if (!Rs2Player.getWorldLocation().equals(bossWorldPoint)) { - Rs2Walker.walkFastCanvas(bossWorldPoint); - sleepUntil(() -> Rs2Player.getWorldLocation().equals(bossWorldPoint)); + + final int reachedDistance = 3; + final long deadlineMs = System.currentTimeMillis() + 90_000L; + while (System.currentTimeMillis() < deadlineMs) { + WorldPoint loc = Rs2Player.getWorldLocation(); + if (loc != null && loc.distanceTo(bossWorldPoint) <= reachedDistance) { + break; + } + Rs2Walker.walkWithState(bossWorldPoint, reachedDistance); + sleep(600); } - if (Rs2Player.getWorldLocation().distanceTo(bossWorldPoint) <= 3) { + + WorldPoint finalLoc = Rs2Player.getWorldLocation(); + if (finalLoc != null && finalLoc.distanceTo(bossWorldPoint) <= reachedDistance) { if (debugLogging) {Microbot.log("Arrived at " + bossName + " lobby");} return; } @@ -72,7 +85,7 @@ public void enterBossArena(String bossName, int bossStatueID, WorldPoint bossWor } sleepUntil(() -> Rs2Player.getWorldLocation().equals(bossWorldPoint)); } - if (Rs2GameObject.interact(bossStatueID, "Use")) { + if (Microbot.getRs2TileObjectCache().query().interact(bossStatueID, "Use")) { if (debugLogging) {Microbot.log("Entering " + bossName + " arena");} sleepUntil(() -> !Rs2Player.getWorldLocation().equals(bossWorldPoint),5_000); } @@ -133,7 +146,7 @@ public static void meleePrayerOn() { * Returns true if the sigil NPC (the highlighted attack tile) is present */ public static boolean isNormalAttackSequence(int sigilNpcID) { - return Rs2Npc.getNpc(sigilNpcID) != null; + return Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null; } /** @@ -161,7 +174,7 @@ public void normalAttackSequence(int sigilNpcID, while (sigilMoves <= 3 && isNormalAttackSequence(sigilNpcID)) { /* 1 ─ detect a new sigil square */ - Rs2NpcModel sigil = Rs2Npc.getNpc(sigilNpcID); + Rs2NpcModel sigil = Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest(); if (sigil == null) { sleep(300); if (debugLogging) {Microbot.log("Sigil not found. Breaking out of sequence");} @@ -193,10 +206,10 @@ public void normalAttackSequence(int sigilNpcID, } /* 4 ─ attack the boss whenever not in combat */ - Rs2NpcModel boss = Rs2Npc.getNpc(bossNpcID); + Rs2NpcModel boss = Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest(); if (boss != null && !Rs2Combat.inCombat()) { if (debugLogging) {Microbot.log("Attacking the boss");} - Rs2Npc.attack(bossNpcID); + Microbot.getRs2NpcCache().query().withId(bossNpcID).interact("Attack"); } sleep(300); @@ -215,7 +228,7 @@ public void bossBailOut(WorldPoint bailOutLocation) { long endTime = System.currentTimeMillis() + 10_000; while (System.currentTimeMillis() < endTime) { - if (Rs2GameObject.interact(exitStairsGroundObjectID)) { + if (Microbot.getRs2TileObjectCache().query().interact(exitStairsGroundObjectID)) { sleepUntil(() -> Rs2Widget.isWidgetVisible(Widgets.BOSS_HEALTH_BAR.getID()),5_000); if (debugLogging) {Microbot.log("Successfully bailed out of the boss arena");} return; @@ -230,8 +243,8 @@ public void bossBailOut(WorldPoint bailOutLocation) { /** If current run energy is less than 80%, recharges run energy at a campfire located on the world canvas */ public static void rechargeRunEnergy() { - if (Rs2GameObject.getGameObject(ObjectID.PMOON_RANGE) != null && Rs2Player.getRunEnergy() <=80) { - Rs2GameObject.interact(ObjectID.PMOON_RANGE, "Make-cuppa"); + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_RANGE).nearest() != null && Rs2Player.getRunEnergy() <=80) { + Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_RANGE, "Make-cuppa"); sleep(600); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java index 99689789b5..893bdc548b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java @@ -11,7 +11,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -81,7 +80,7 @@ private boolean retrieveDeathItems() { if (debugLogging) {Microbot.log("Attempting to walk back to grave site");} Rs2Walker.walkTo(graveLocation, 2); sleepUntil(() -> (Rs2Player.getWorldLocation().distanceTo(graveLocation) <= 3), 60_000); - if (Rs2Npc.interact(NpcID.GRAVESTONE_DEFAULT, "Loot")) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.GRAVESTONE_DEFAULT).interact("Loot")) { if (debugLogging) {Microbot.log("Successfully looted gravestone");} return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java index 203adf937d..a4eb640a6d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java @@ -13,8 +13,7 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.coords.Rs2LocalPoint; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; @@ -74,7 +73,7 @@ public State execute() { sleepUntil(() -> Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 5_000); } int bossNpcID = NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -98,15 +97,17 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the eclipseMoonShield NPC is found. */ public boolean isSpecialAttack1Sequence() { - Rs2NpcModel eclipseMoonShield = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD); - return eclipseMoonShield != null && Rs2Npc.getNpc(sigilNpcID) == null; + Rs2NpcModel eclipseMoonShield = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).nearest(); + return eclipseMoonShield != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null; } /** Eclipse – Moon Shield Special-Attack Handler */ public void specialAttack1Sequence() { Rs2Prayer.disableAllPrayers(); - WorldPoint spawn = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).getWorldLocation(); + var shieldNpc = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).nearest(); + if (shieldNpc == null) return; + WorldPoint spawn = shieldNpc.getWorldLocation(); if (debugLogging) {Microbot.log("Exact Moonshield location = " + spawn);} /*if we enter arena mid attack phase, bail out*/ if (!spawn.equals(shieldSpawnTile)) { @@ -152,7 +153,7 @@ public void specialAttack1Sequence() if (debugLogging) {Microbot.log("Running to the normal attack sequence tile");} Rs2Walker.walkFastCanvas(fin, true); if (debugLogging) {Microbot.log("Sleeping until the Sigil tile spawns");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null, 4_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null, 4_000); if (debugLogging) {Microbot.log("Searing Rays phase finished");} } @@ -168,7 +169,7 @@ public boolean isSpecialAttack2Sequence() { WorldPoint center = cloneAttackTile; WorldPoint playerTile = Rs2Player.getWorldLocation(); - Rs2NpcModel bossNPC = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS); + Rs2NpcModel bossNPC = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS).nearest(); // 1. Captures the conditions required for the start of the special attack sequence. if (playerTile.equals(center) && Rs2Player.getAnimation() == AnimationID.HUMAN_TROLL_FLYBACK_MERGE) { @@ -183,7 +184,7 @@ public boolean isSpecialAttack2Sequence() } // 2. Captures the conditions required if we spawn into the arena midway through the special attack phase. - if (playerTile.equals(center) && bossNPC != null && Rs2Npc.getNpc(sigilNpcID) == null && !bossNPC.getLocalLocation().equals(Rs2LocalPoint.fromWorldInstance(center))) { + if (playerTile.equals(center) && bossNPC != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null && !bossNPC.getLocalLocation().equals(Rs2LocalPoint.fromWorldInstance(center))) { boss.equipInventorySetup(equipmentClones); BossHandler.meleePrayerOn(); return true; @@ -206,10 +207,10 @@ public void specialAttack2Sequence() { // 1. Look at ALL Eclipse-Moon NPCs this tick that have the specific spawn animation. Game mechanics mean this list SHOULD only return 1 NPC - List spawningClones = Rs2Npc - .getNpcs(n -> n.getId() == CLONE_NPC_ID - && n.getAnimation() == CLONE_SPAWN_ANIM) - .collect(Collectors.toList()); + List spawningClones = Microbot.getRs2NpcCache().query() + .where(n -> n.getId() == CLONE_NPC_ID + && n.getNpc().getAnimation() == CLONE_SPAWN_ANIM) + .toList(); if (debugLogging) {Microbot.log("Collected all NPCs that match NPC ID & NPC Animation. Total = " + spawningClones.size());} // 2. Find the first clone within the list that matches the filter diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java index 21856dc944..d10598a152 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java @@ -7,7 +7,6 @@ import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.moonsofperil.enums.State; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -73,7 +72,7 @@ private void makeMoonlightPotions(int moonlightPotionsQuantum) { /* Take herblore supplies */ while (Rs2Inventory.count(ItemID.VIAL_WATER) < amountToCreate) { if (debugLogging) {Microbot.log("Take herblore supplies from supply crate");} - if (Rs2GameObject.interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { Rs2Dialogue.sleepUntilHasDialogueOption("Take herblore supplies."); Rs2Dialogue.clickOption("Take herblore supplies."); Rs2Inventory.waitForInventoryChanges(1_500); @@ -84,7 +83,7 @@ private void makeMoonlightPotions(int moonlightPotionsQuantum) { while (Rs2Inventory.count(ItemID.MOONLIGHT_GRUB) + Rs2Inventory.count(ItemID.MOONLIGHT_GRUB_PASTE) < amountToCreate) { if (debugLogging) {Microbot.log("Collect Moonlight Grub");} - if (Rs2GameObject.interact(ObjectID.PMOON_GRUB_SAPLING, "Collect-from")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_GRUB_SAPLING, "Collect-from")) { sleepUntil(() -> Rs2Inventory.count(ItemID.MOONLIGHT_GRUB) + Rs2Inventory.count(ItemID.MOONLIGHT_GRUB_PASTE) >= amountToCreate, 8_000); } @@ -130,7 +129,7 @@ private boolean ensureBigNet() { long start = System.currentTimeMillis(); while (!Rs2Inventory.contains(ItemID.BIG_NET) && System.currentTimeMillis() - start < 15_000) { - if (Rs2GameObject.interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { // interact exists :contentReference[oaicite:1]{index=1} + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { // interact exists :contentReference[oaicite:1]{index=1} Rs2Dialogue.sleepUntilHasDialogueOption("Take fishing supplies."); Rs2Dialogue.clickOption("Take fishing supplies."); @@ -182,7 +181,7 @@ private void fishBream() { } if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(51367, "Fish"); + Microbot.getRs2TileObjectCache().query().interact(51367, "Fish"); sleep(3000, 4000); } sleep(300, 500); @@ -201,7 +200,7 @@ private void cookBream() { while (Rs2Inventory.contains(ItemID.BREAM_FISH_RAW)) { if (!Rs2Player.isAnimating()) { - if (Rs2GameObject.interact(ObjectID.PMOON_RANGE, "Cook")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_RANGE, "Cook")) { sleep(600, 900); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java index 1ef0ad2aba..66a14163c2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.moonsofperil.enums.State; import net.runelite.client.plugins.microbot.moonsofperil.enums.Widgets; import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import javax.inject.Inject; @@ -51,7 +50,7 @@ public boolean validate() { public State execute() { BreakHandlerScript.setLockState(true); boss.walkToBoss(null, "Rewards Chest", rewardChestLocation); - if (Rs2GameObject.interact(lunarChestGameObjectID, "Claim")) { + if (Microbot.getRs2TileObjectCache().query().interact(lunarChestGameObjectID, "Claim")) { if (debugLogging) {Microbot.log("Successfully claimed rewards from Lunar Chest");} rewardChestCount.incrementAndGet(); sleep(2_400); diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java index b28a3c0145..2f25e32ca8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.nmz; +import net.runelite.api.gameval.VarbitID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -40,7 +41,17 @@ public Dimension render(Graphics2D graphics) { .left("Will drink absorption at: " + NmzScript.minAbsorption) .build()); - } catch(Exception ex) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Overload (barrel):") + .right(String.valueOf(Microbot.getVarbitValue(VarbitID.NZONE_POTION_3))) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Absorb (barrel):") + .right(String.valueOf(Microbot.getVarbitValue(VarbitID.NZONE_POTION_4))) + .build()); + +} catch(Exception ex) { System.out.println(ex.getMessage()); } return super.render(graphics); diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java index d54ff0e078..cf51aa96ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java @@ -31,7 +31,7 @@ ) @Slf4j public class NmzPlugin extends Plugin { - final static String version = "2.3.1"; + final static String version = "2.4.1"; @Inject private NmzConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java index 3fde086fc0..a16509d370 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java @@ -2,25 +2,34 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.*; +import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarbitID; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; +import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; +import net.runelite.client.plugins.microbot.util.antiban.enums.PlayStyle; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; import net.runelite.client.plugins.microbot.util.security.Encryption; -import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.security.LoginManager; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -28,9 +37,6 @@ import javax.inject.Inject; import java.util.concurrent.TimeUnit; -import static net.runelite.api.ObjectID.OVERLOAD_POTION; -import static net.runelite.api.Varbits.NMZ_ABSORPTION; - public class NmzScript extends Script { private NmzConfig config; @@ -51,6 +57,11 @@ public class NmzScript extends Script { private boolean initialized = false; private long lastCombatTime = 0; + @Inject + private Rs2TileObjectCache tileObjectCache; + @Inject + private Rs2NpcCache npcCache; + public boolean canStartNmz() { return Rs2Inventory.count("overload (4)") == config.overloadPotionAmount() || (Rs2Inventory.hasItem("prayer potion") && config.togglePrayerPotions()); @@ -66,32 +77,49 @@ public NmzScript(NmzPlugin plugin, NmzConfig config) { public boolean run() { prayerPotionScript = new PrayerPotionScript(); Microbot.getSpecialAttackConfigs().setSpecialAttack(true); + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.setActivity(Activity.GENERAL_COMBAT); + Rs2Antiban.setActivityIntensity(ActivityIntensity.LOW); + Rs2Antiban.setPlayStyle(PlayStyle.MODERATE); + Rs2Antiban.activateAntiban(); + Rs2AntibanSettings.moveMouseOffScreen = true; + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.naturalMouse = true; + Rs2AntibanSettings.usePlayStyle = true; + Rs2AntibanSettings.behavioralVariability = true; + Rs2AntibanSettings.nonLinearIntervals = true; + Rs2AntibanSettings.actionCooldownChance = 0.00; + Rs2AntibanSettings.moveMouseOffScreenChance = 1.00; + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn()) return; if (!initialized) { initialized = true; - if (config.inventorySetupon()) { - if (config.inventorySetup() != null) { - var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); - if (!inventorySetup.doesInventoryMatch() || !inventorySetup.doesEquipmentMatch()) { - Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); - if (!inventorySetup.loadEquipment() || !inventorySetup.loadInventory()) { - Microbot.log("Failed to load inventory setup"); - Microbot.stopPlugin(plugin); - return; + // Skip inventory setup and lobby walk if already inside the NMZ instance + boolean isInNmzInstance = Microbot.getClient().getLocalPlayer().getWorldLocation().getY() > 4500; + if (!isInNmzInstance) { + if (config.inventorySetupon()) { + if (config.inventorySetup() != null) { + var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); + if (!inventorySetup.doesInventoryMatch() || !inventorySetup.doesEquipmentMatch()) { + Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); + if (!inventorySetup.loadEquipment() || !inventorySetup.loadInventory()) { + Microbot.log("Failed to load inventory setup"); + Microbot.stopPlugin(plugin); + return; + } + Rs2Bank.closeBank(); } - Rs2Bank.closeBank(); } } + Rs2Walker.walkTo(new WorldPoint(2609, 3114, 0), 5); } - Rs2Walker.walkTo(new WorldPoint(2609, 3114, 0), 5); } if (!super.run()) return; - Rs2Combat.enableAutoRetialiate(); - if (Rs2Random.between(1, 50) == 1 && config.randomMouseMovements()) { - Microbot.getMouse().click(Rs2Random.between(0, Microbot.getClient().getCanvasWidth()), Rs2Random.between(0, Microbot.getClient().getCanvasHeight()), true); - } + if (Rs2AntibanSettings.actionCooldownActive) return; + Rs2Combat.setAutoRetaliate(true); boolean isOutsideNmz = isOutside(); useOverload = Microbot.getClient().getBoostedSkillLevel(Skill.RANGED) == Microbot.getClient().getRealSkillLevel(Skill.RANGED) && config.overloadPotionAmount() > 0; if (isOutsideNmz) { @@ -110,6 +138,8 @@ public boolean run() { @Override public void shutdown() { super.shutdown(); + Rs2Antiban.deactivateAntiban(); + Rs2Antiban.resetAntibanSettings(); initialized = false; } @@ -119,7 +149,7 @@ public boolean isOutside() { } public void handleOutsideNmz() { - boolean hasStartedDream = Microbot.getVarbitValue(3946) > 0; + boolean hasStartedDream = Microbot.getVarbitValue(VarbitID.NZONE_PURCHASEDDREAM) > 0; if (config.togglePrayerPotions()) Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, false); if (!hasStartedDream) { @@ -127,12 +157,12 @@ public void handleOutsideNmz() { } else { final String overload = "Overload (4)"; final String absorption = "Absorption (4)"; - storePotions(OVERLOAD_POTION, "overload", config.overloadPotionAmount()); - storePotions(ObjectID.ABSORPTION_POTION, "absorption", config.absorptionPotionAmount()); + storePotions(ObjectID.NZONE_BARREL_3, "overload", config.overloadPotionAmount()); + storePotions(ObjectID.NZONE_BARREL_4, "absorption", config.absorptionPotionAmount()); handleStore(); - fetchOverloadPotions(OVERLOAD_POTION, overload, config.overloadPotionAmount()); + fetchOverloadPotions(ObjectID.NZONE_BARREL_3, overload, config.overloadPotionAmount()); if (Rs2Inventory.hasItemAmount(overload, config.overloadPotionAmount())) { - fetchPotions(ObjectID.ABSORPTION_POTION, absorption, config.absorptionPotionAmount()); + fetchPotions(ObjectID.NZONE_BARREL_4, absorption, config.absorptionPotionAmount()); } } if (canStartNmz()) { @@ -146,11 +176,13 @@ public void handleInsideNmz() { if (Rs2Player.isInCombat()) { lastCombatTime = System.currentTimeMillis(); } + Rs2Antiban.takeMicroBreakByChance(); if (!Rs2Player.isInCombat() && System.currentTimeMillis() - lastCombatTime > 20000) { - Rs2NpcModel closestNpc = Rs2Npc.getNearestNpcWithAction("Attack"); - + Rs2NpcModel closestNpc = npcCache.query().nearest(); if (closestNpc != null) { - Rs2Npc.interact(closestNpc, "Attack"); + if (closestNpc.click("Attack")) { + Rs2Antiban.actionCooldown(); + } } } prayerPotionScript.run(); @@ -173,7 +205,8 @@ private void walkToCenter() { public void startNmzDream() { // Set new center so that it is random for every time joining the dream center = new WorldPoint(Rs2Random.between(2270, 2276), Rs2Random.between(4693, 4696), 0); - Rs2Npc.interact(NpcID.DOMINIC_ONION, "Dream"); + Rs2NpcModel dominic = npcCache.query().withName("Dominic Onion").nearestOnClientThread(); + if (dominic != null) dominic.click("Dream"); sleepUntil(() -> Rs2Widget.hasWidget("Which dream would you like to experience?")); Rs2Widget.clickWidget("Previous:"); sleepUntil(() -> Rs2Widget.hasWidget("Click here to continue")); @@ -188,28 +221,34 @@ public void startNmzDream() { public boolean useOrbs() { boolean orbHasSpawned = false; if (config.useZapper()) { - orbHasSpawned = interactWithObject(ObjectID.ZAPPER_26256); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_ZAPPER); } if (config.useReccurentDamage()) { - orbHasSpawned = interactWithObject(ObjectID.RECURRENT_DAMAGE); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_DAMAGEMULTIPLIER); } if (config.usePowerSurge()) { - orbHasSpawned = interactWithObject(ObjectID.POWER_SURGE); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_SPECIALATTACK); } return orbHasSpawned; } public boolean interactWithObject(int objectId) { - TileObject rs2GameObject = Rs2GameObject.findObjectById(objectId); - if (rs2GameObject != null) { - Rs2Walker.walkFastLocal(rs2GameObject.getLocalLocation()); - sleepUntil(() -> { - WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - return loc != null && loc.distanceTo(rs2GameObject.getWorldLocation()) < 5; - }); - Rs2GameObject.interact(objectId); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj != null) { + sleep(1000, 15000); + WorldPoint playerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + if (playerLoc != null && playerLoc.distanceTo(obj.getWorldLocation()) >= 15) { + Rs2Walker.walkFastLocal(obj.getLocalLocation()); + sleepUntil(() -> { + WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + return loc != null && loc.distanceTo(obj.getWorldLocation()) < 15; + }, 10000); + } + obj.click(); + // Wait for the power-up to despawn to prevent repeated clicks on the same orb + sleepUntil(() -> tileObjectCache.query().withId(objectId).nearest() == null, 3000); return true; } return false; @@ -222,13 +261,17 @@ private void fetchOverloadPotions(int objectId, String itemName, int requiredAmo int neededAmount = requiredAmount - currentAmount; - Rs2GameObject.interact(objectId, "Take"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); if (Rs2Widget.hasWidget(widgetText)) { // Each potion has 4 doses, so request the correct number of doses + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString(Integer.toString(neededAmount * 4)); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> Rs2Inventory.count(itemName) == requiredAmount); } @@ -246,10 +289,12 @@ public void manageSelfHarm() { && (!hasOverloadPotions || currentRangedLevel != realRangedLevel)) { maxHealth = 1; - if (Rs2Inventory.hasItem(ItemID.LOCATOR_ORB)) { - Rs2Inventory.interact(ItemID.LOCATOR_ORB, "feel"); - } else if (Rs2Inventory.hasItem(ItemID.DWARVEN_ROCK_CAKE_7510)) { - Rs2Inventory.interact(ItemID.DWARVEN_ROCK_CAKE_7510, "guzzle"); + if (Rs2Inventory.hasItem(ItemID.DS2_ORB)) { + Rs2Inventory.interact(ItemID.DS2_ORB, "feel"); + Rs2Antiban.actionCooldown(); + } else if (Rs2Inventory.hasItem(ItemID.HUNDRED_DWARF_COOL_ROCKCAKE)) { + Rs2Inventory.interact(ItemID.HUNDRED_DWARF_COOL_ROCKCAKE, "guzzle"); + Rs2Antiban.actionCooldown(); } if (currentHP == 1) { @@ -278,7 +323,7 @@ public void useOverloadPotion() { } public void useAbsorptionPotion() { - if (Microbot.getVarbitValue(NMZ_ABSORPTION) < minAbsorption && Rs2Inventory.hasItem("absorption")) { + if (Microbot.getVarbitValue(VarbitID.NZONE_ABSORB_POTION_EFFECTS) < minAbsorption && Rs2Inventory.hasItem("absorption")) { for (int i = 0; i < Rs2Random.between(4, 8); i++) { Rs2Inventory.interact(x -> x.getName().toLowerCase().contains("absorption"), "drink"); sleep(600, 1000); @@ -291,11 +336,15 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; if (Rs2Inventory.get(itemName) == null) return; - Rs2GameObject.interact(objectId, "Store"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Store"); String storeWidgetText = "Store all your "; sleepUntil(() -> Rs2Widget.hasWidget(storeWidgetText)); if (Rs2Widget.hasWidget(storeWidgetText)) { + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString("1"); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> !Rs2Inventory.hasItem(objectId)); Rs2Inventory.dropAll(itemName); @@ -305,53 +354,65 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { private void fetchPotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; - Rs2GameObject.interact(objectId, "Take"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); if (Rs2Widget.hasWidget(widgetText)) { + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString(Integer.toString(requiredAmount * 4)); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> Rs2Inventory.count(itemName) == requiredAmount); } } public void consumeEmptyVial() { - final int EMPTY_VIAL = 26291; if (Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2Widget.getWidget(129, 6) == null || Rs2Widget.getWidget(129, 6).isHidden()) .orElse(false)) { - Rs2GameObject.interact(EMPTY_VIAL, "drink"); + Rs2TileObjectModel vial = tileObjectCache.query().withId(ObjectID.NZONE_LOBBY_VIAL).nearest(); + if (vial != null) vial.click("drink"); } - sleep(2000,4000); + sleep(2000, 4000); Widget widget = Rs2Widget.getWidget(129, 6); if (!Microbot.getClientThread().runOnClientThreadOptional(widget::isHidden).orElse(false)) { Rs2Widget.clickWidget(widget.getId()); sleep(300); Rs2Widget.clickWidget(widget.getId()); } - sleep(2000,4000); + sleep(2000, 4000); } public void handleStore() { if (canStartNmz()) return; - int varbitOverload = 3953; - int varbitAbsorption = 3954; - int overloadAmt = Microbot.getVarbitValue(varbitOverload); - int absorptionAmt = Microbot.getVarbitValue(varbitAbsorption); - int nmzPoints = Microbot.getVarbitPlayerValue(VarPlayer.NMZ_REWARD_POINTS); + int overloadAmt = Microbot.getVarbitValue(VarbitID.NZONE_POTION_3); + int absorptionAmt = Microbot.getVarbitValue(VarbitID.NZONE_POTION_4); - if (absorptionAmt > config.absorptionPotionAmount() * 4 && overloadAmt > config.overloadPotionAmount() * 4) - return; + // Varbits are in doses; config is in 4-dose potions + int overloadDosesNeeded = Math.max(0, config.overloadPotionAmount() * 4 - overloadAmt); + int absorptionDosesNeeded = Math.max(0, config.absorptionPotionAmount() * 4 - absorptionAmt); - if (!Rs2Inventory.isFull()) { - if ((absorptionAmt < (config.absorptionPotionAmount() * 4) || overloadAmt < config.overloadPotionAmount() * 4) && nmzPoints < 100000) { - Microbot.showMessage("BOT SHUTDOWN: Not enough points to buy potions"); - Microbot.stopPlugin(plugin); - return; - } + if (overloadDosesNeeded == 0 && absorptionDosesNeeded == 0) return; + + // Each shop purchase gives one 4-dose potion (ceiling division) + int overloadToBuy = (overloadDosesNeeded + 3) / 4; + int absorptionToBuy = (absorptionDosesNeeded + 3) / 4; + + // NMZ reward shop costs: Overload 1,500 pts / Absorption 1,000 pts per 4-dose potion + int totalCost = overloadToBuy * 1500 + absorptionToBuy * 1000; + int nmzPoints = Microbot.getVarbitPlayerValue(VarPlayerID.NZONE_REWARDPOINTS); + + if (nmzPoints < totalCost) { + Microbot.showMessage("BOT SHUTDOWN: Not enough points to buy potions (have " + nmzPoints + ", need " + totalCost + ")"); + Microbot.stopPlugin(plugin); + return; } - Rs2GameObject.interact(26273); + Rs2TileObjectModel chest = tileObjectCache.query().withId(ObjectID.NZONE_LOBBY_CHEST).nearest(); + if (chest == null) return; + chest.click(); sleepUntil(() -> Rs2Widget.isWidgetVisible(13500418) || Rs2Bank.isBankPinWidgetVisible(), 10000); if (Rs2Bank.isBankPinWidgetVisible()) { try { @@ -364,20 +425,26 @@ public void handleStore() { Widget benefitsBtn = Rs2Widget.getWidget(13500418); if (benefitsBtn == null) return; - boolean notSelected = benefitsBtn.getSpriteId() != 813; - if (notSelected) { + if (benefitsBtn.getSpriteId() != 813) { Rs2Widget.clickWidgetFast(benefitsBtn, 4, 4); + sleepUntil(() -> { + Widget btn = Rs2Widget.getWidget(13500418); + return btn != null && btn.getSpriteId() == 813; + }, 3000); } - int count = 0; - while (count < Rs2Random.between(3, 5)) { + + for (int i = 0; i < overloadToBuy; i++) { + Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); + if (nmzRewardShop == null) break; + Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(6), 6, 4); + sleep(600, 1000); + } + + for (int i = 0; i < absorptionToBuy; i++) { Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); if (nmzRewardShop == null) break; - Widget overload = nmzRewardShop.getChild(6); - Rs2Widget.clickWidgetFast(overload, 6, 4); - Widget absorption = nmzRewardShop.getChild(9); - Rs2Widget.clickWidgetFast(absorption, 9, 4); - sleep(600, 1200); - count++; + Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(9), 9, 4); + sleep(600, 1000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java index 18584f4680..f818de481b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class npcTannerPlugin extends Plugin { - public static final String version = "2.0.1"; + public static final String version = "2.0.2"; @Inject private npcTannerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java index a7eb0d9a65..199cd4d774 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java @@ -9,7 +9,7 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -104,7 +104,8 @@ public void WalkToandTan(npcTannerConfig config){ } } else { Microbot.status="Tanning: "+npcTannerScript.whattotan; - if(Rs2Npc.interact(Rs2Npc.getNpc("Ellis"), "Trade")){ + Rs2NpcModel ellis = Microbot.getRs2NpcCache().query().withName("Ellis").nearestOnClientThread(); + if(ellis != null && ellis.click("Trade")){ sleep(1000, 3000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java b/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java index 8621823c0f..9af26d5d30 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java @@ -14,6 +14,7 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -237,7 +238,7 @@ public boolean run() { } Rs2Magic.cast(MagicAction.CHARGE_AIR_ORB); - Rs2GameObject.interact(ObjectID.OBELISK_OF_AIR); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.OBELISK_OF_AIR); Rs2Dialogue.sleepUntilHasCombinationDialogue(); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleepUntil(() -> Rs2Player.isAnimating(1200), 5000); @@ -246,7 +247,7 @@ public boolean run() { sleepUntil(() -> !Rs2Player.isAnimating(5000) || !Rs2Inventory.hasItem(ItemID.STAFFORB) || shouldFlee, () -> shouldFlee = !plugin.getDangerousPlayers().isEmpty(), 96000, 1000); break; case DRINKING: - Rs2GameObject.interact(ObjectID.POOL_OF_REFRESHMENT); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.POOL_OF_REFRESHMENT); sleepUntil(() -> Rs2Player.getRunEnergy() == 100 && !Rs2Player.isAnimating(2000)); break; case WALKING: @@ -410,9 +411,9 @@ private boolean shouldDrinkFromPool() { if (!hasRequiredItems()) return false; if (!Rs2GameObject.exists(ObjectID.POOL_OF_REFRESHMENT)) return false; - TileObject refreshmentPool = Rs2GameObject.findObjectById(ObjectID.POOL_OF_REFRESHMENT); + Rs2TileObjectModel refreshmentPool = Microbot.getRs2TileObjectCache().query().withId(ObjectID.POOL_OF_REFRESHMENT).nearest(); if (refreshmentPool == null) return false; - + return Rs2Player.getWorldLocation().distanceTo(refreshmentPool.getWorldLocation()) < 8; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java b/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java index 1d9f035cdc..ee71e03bf8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java @@ -11,7 +11,6 @@ import java.util.stream.Stream; import javax.inject.Inject; import net.runelite.api.Constants; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -28,7 +27,6 @@ import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.inventory.RunePouchType; @@ -37,8 +35,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.skillcalculator.skills.MagicAction; @@ -146,7 +143,7 @@ public boolean run() Rs2Inventory.emptyPouches(); return; } - Rs2GameObject.interact(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR, "craft-rune"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR).interact("craft-rune"); Rs2Inventory.waitForInventoryChanges(5000); break; case RESETTING: @@ -169,8 +166,7 @@ public boolean run() if (config.directInteract() && Microbot.isPluginEnabled(GpuPlugin.class)) { - GameObject ladder = Rs2GameObject.getGameObject(ObjectID.RC_ZMI_DUNGEON_ENTRANCE); - Rs2GameObject.interact(ladder, "Climb"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_ENTRANCE).interact("Climb"); sleepUntil(this::isNearEniola, 20000); } else @@ -188,12 +184,12 @@ public boolean run() if (!Rs2Bank.isOpen()) { - Rs2NpcModel eniola = Rs2Npc.getNpc(NpcID.RC_ZMI_BANKER); + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query().withId(NpcID.RC_ZMI_BANKER).nearest(); if (eniola == null) { return; } - Rs2Npc.interact(eniola, "bank"); + eniola.click("bank"); sleepUntil(Rs2Bank::isOpen, 3000); return; } @@ -338,7 +334,7 @@ else if (hasStaminaPotion) { if (config.directInteract() && Microbot.isPluginEnabled(GpuPlugin.class)) { - GameObject altarObject = Rs2GameObject.getGameObject(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR, Constants.SCENE_SIZE); + var altarModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR).within(Constants.SCENE_SIZE).nearest(); if (Rs2Camera.getPitch() < 210 || Rs2Camera.getPitch() > 280) { int randomPitch = Rs2Random.nextInt(220, 260, 1, false); @@ -351,7 +347,7 @@ else if (hasStaminaPotion) sleepUntil(() -> Rs2Camera.getZoom() == 128); } - Rs2GameObject.interact(altarObject, "craft-rune"); + if (altarModel != null) altarModel.click("craft-rune"); sleepUntil(this::isNearAltar, 30000); } else @@ -361,7 +357,7 @@ else if (hasStaminaPotion) } else { - Rs2GameObject.interact(ObjectID.RC_ZMI_DUNGEON_WALL_CRACK_ENTRANCE, "squeeze-through"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_WALL_CRACK_ENTRANCE).interact("squeeze-through"); sleepUntil(this::isNearAltar, 10000); } break; @@ -452,7 +448,7 @@ private boolean isNearAltar() private boolean isNearEniola() { - Rs2NpcModel eniola = Rs2Npc.getNpc(NpcID.RC_ZMI_BANKER); + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query().withId(NpcID.RC_ZMI_BANKER).nearest(); if (eniola == null) { return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java index 9aa4b1aab9..243dd15d59 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class PestControlPlugin extends Plugin { - static final String version = "2.2.9"; + static final String version = "2.3.4"; @Inject PestControlScript pestControlScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 1e216de201..1c2cc51079 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -1,24 +1,33 @@ package net.runelite.client.plugins.microbot.pestcontrol; import com.google.common.collect.ImmutableSet; +import net.runelite.api.Actor; +import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.NPCComposition; import net.runelite.api.NpcID; import net.runelite.api.ObjectID; +import net.runelite.api.Player; import net.runelite.api.Skill; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.ComponentID; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetupsItem; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; + +import net.runelite.client.plugins.microbot.util.misc.SpecialAttackWeaponEnum; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -37,6 +46,7 @@ public class PestControlScript extends Script { boolean initialise = true; boolean walkToCenter = false; + private boolean wasInPestControl = false; PestControlConfig config; private final PestControlPlugin plugin; @@ -74,6 +84,21 @@ private void resetPortals() { } } + private static WorldPoint stepTowards(WorldPoint from, WorldPoint to, int maxStep) { + int dx = to.getX() - from.getX(); + int dy = to.getY() - from.getY(); + int chebyshev = Math.max(Math.abs(dx), Math.abs(dy)); + if (chebyshev <= maxStep) { + return to; + } + double scale = (double) maxStep / chebyshev; + return new WorldPoint( + from.getX() + (int) Math.round(dx * scale), + from.getY() + (int) Math.round(dy * scale), + from.getPlane() + ); + } + public boolean run(PestControlConfig config) { this.config = config; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { @@ -98,7 +123,9 @@ public boolean run(PestControlConfig config) { } if (Rs2Player.getWorldLocation().getRegionID() == 10537 && Rs2Player.getWorld() == config.world()) { - initialise = handleInventorySetup(); + if (handleInventorySetup()) { + initialise = false; + } } else { Microbot.log("Traveling to Pest Island"); @@ -107,6 +134,7 @@ public boolean run(PestControlConfig config) { } if (isInPestControl) { initialise = false; + wasInPestControl = true; if (!isQuickPrayerEnabled() && Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) != 0 && config.quickPrayer()) { final Widget prayerOrb = Rs2Widget.getWidget(ComponentID.MINIMAP_QUICK_PRAYER_ORB); if (prayerOrb != null) { @@ -115,26 +143,31 @@ public boolean run(PestControlConfig config) { } } if (!walkToCenter) { - WorldPoint worldPoint = WorldPoint.fromRegion(Rs2Player.getWorldLocation().getRegionID(), 32, 17, Microbot.getClient().getTopLevelWorldView().getPlane()); - Rs2Walker.walkTo(worldPoint, 3); - if (worldPoint.distanceTo(Rs2Player.getWorldLocation()) > 4) { - return; - } else { + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + WorldPoint worldPoint = WorldPoint.fromRegion(playerLoc.getRegionID(), 32, 17, playerLoc.getPlane()); + if (playerLoc.distanceTo(worldPoint) <= 4) { walkToCenter = true; + } else { + Rs2Walker.walkMiniMap(stepTowards(playerLoc, worldPoint, 14)); + sleepUntil(() -> !Rs2Player.isMoving(), 4000); + return; } } - Rs2Combat.setSpecState(true, config.specialAttackPercentage() * 10); + activateSpecialAttackIfReady(); Widget activity = Rs2Widget.getWidget(26738700); //145 = 100% if (activity != null && activity.getChild(0).getWidth() <= 20 && !Rs2Combat.inCombat()) { - Optional attackableNpc = Rs2Npc.getAttackableNpcs().findFirst(); - attackableNpc.ifPresent(rs2NpcModel -> Rs2Npc.interact(rs2NpcModel.getId(), "attack")); + Rs2NpcModel attackableNpc = Microbot.getClientThread().invoke(() -> + Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) + .nearest()); + if (attackableNpc != null) attackableNpc.click("Attack"); return; } - var brawler = Rs2Npc.getNpc("brawler"); + var brawler = Microbot.getRs2NpcCache().query().withName("brawler").nearestOnClientThread(); if (brawler != null && brawler.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 3) { - Rs2Npc.interact(brawler, "attack"); + brawler.click("Attack"); sleepUntil(() -> !Rs2Combat.inCombat()); return; } @@ -159,35 +192,46 @@ public boolean run(PestControlConfig config) { || handleAttack(PestControlNpc.SPINNER, 3)) { return; } - Rs2NpcModel portal = Arrays.stream(Rs2Npc.getPestControlPortals()).findFirst().orElse(null); + Rs2NpcModel portal = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("portal") + && n.getNpc() != null && !n.getNpc().isDead() + && Arrays.stream(Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getClient().getNpcDefinition(n.getId()).getActions()).orElse(new String[0])) + .anyMatch(a -> a != null && a.equalsIgnoreCase("attack"))) + .nearest(); if (portal != null) { - if (Rs2Npc.interact(portal.getId(), "attack")) { + if (portal.click("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); } } else { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Optional attackableNpc = Rs2Npc.getAttackableNpcs().findFirst(); - attackableNpc.ifPresent(rs2NpcModel -> Rs2Npc.interact(rs2NpcModel.getId(), "attack")); + Rs2NpcModel attackableNpc = Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) + .nearestOnClientThread(); + if (attackableNpc != null) attackableNpc.click("Attack"); } } } else { - Rs2Walker.setTarget(null); + if (wasInPestControl) { + Rs2Walker.setTarget(null); + wasInPestControl = false; + } resetPortals(); walkToCenter = false; - sleep(Rs2Random.between(1600, 1800)); if (!isInBoat && !initialise) { if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 100) { - Rs2GameObject.interact(ObjectID.GANGPLANK_25632); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_25632); } else if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 70) { - Rs2GameObject.interact(ObjectID.GANGPLANK_25631); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_25631); } else { - Rs2GameObject.interact(ObjectID.GANGPLANK_14315); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_14315); } - sleepUntil(() -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null, 3000); + sleepUntil(this::isInBoat, 3000); } else { if (config.alchInBoat() && !config.alchItem().equalsIgnoreCase("")) { Rs2Magic.alch(config.alchItem()); + sleep(Rs2Random.between(1600, 1800)); } } } @@ -201,15 +245,20 @@ public boolean run(PestControlConfig config) { /** * Handles the inventory setup based on the provided configuration. + * + * @return true when no setup work is needed (no setup configured, already + * matches, or successfully loaded); false when loading failed and + * the script should retry on the next tick. */ private boolean handleInventorySetup() { - if (config.inventorySetup() == null) { - return false; + InventorySetup setup = config.inventorySetup(); + if (setup == null || isEmptySetup(setup)) { + return true; } Microbot.log("Starting Inv Setup"); - var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); + var inventorySetup = new Rs2InventorySetup(setup, mainScheduledFuture); if (inventorySetup.doesInventoryMatch() && inventorySetup.doesEquipmentMatch()) { return true; @@ -222,7 +271,15 @@ private boolean handleInventorySetup() { Microbot.log("Inv Setup Finished"); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 2000); - return false; + return true; + } + + private static boolean isEmptySetup(InventorySetup setup) { + return isAllDummy(setup.getInventory()) && isAllDummy(setup.getEquipment()); + } + + private static boolean isAllDummy(List items) { + return items == null || items.stream().allMatch(item -> item == null || InventorySetupsItem.itemIsDummy(item)); } @@ -232,20 +289,24 @@ public boolean isOutside() { } public boolean isInBoat() { - return Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null; + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null + ).orElse(false); } public boolean isInPestControl() { - return Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BLUE_SHIELD) != null; + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BLUE_SHIELD) != null + ).orElse(false); } public void exitBoat() { if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 100) { - Rs2GameObject.interact(ObjectID.LADDER_25630); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_25630); } else if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 70) { - Rs2GameObject.interact(ObjectID.LADDER_25629); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_25629); } else { - Rs2GameObject.interact(ObjectID.LADDER_14314); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_14314); } sleepUntil(() -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) == null, 3000); @@ -273,7 +334,7 @@ private boolean handleAttack(PestControlNpc npcType, int priority) { } } } else { - if (config.Priority2() == npcType) { + if (config.Priority3() == npcType) { if (npcType == PestControlNpc.BRAWLER) { return attackBrawler(); } else if (npcType == PestControlNpc.PORTAL) { @@ -304,14 +365,29 @@ public Portal getClosestAttackablePortal() { private static boolean attackPortal() { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2NpcModel npcPortal = Rs2Npc.getNpc("portal"); + Rs2NpcModel npcPortal = Microbot.getRs2NpcCache().query().withName("portal").nearestOnClientThread(); if (npcPortal == null) return false; NPCComposition npc = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getNpcDefinition(npcPortal.getId())).orElse(null); if (npc == null) return false; if (Arrays.stream(npc.getActions()).anyMatch(x -> x != null && x.equalsIgnoreCase("attack"))) { - return Rs2Npc.interact(npcPortal, "attack"); + LocalPoint localPoint = npcPortal.getLocalLocation(); + if (localPoint != null && !Rs2Camera.isTileOnScreen(localPoint)) { + WorldPoint npcWp = Microbot.getClientThread().runOnClientThreadOptional(() -> + npcPortal.getNpc().getWorldLocation()).orElse(null); + WorldPoint playerWp = Rs2Player.getWorldLocation(); + if (npcWp != null && playerWp != null) { + int angle = (int) Math.toDegrees(Math.atan2( + npcWp.getY() - playerWp.getY(), + npcWp.getX() - playerWp.getX())); + if (angle < 0) angle += 360; + angle = (angle - 90) % 360; + if (angle < 0) angle += 360; + Rs2Camera.setAngle(angle, 40); + } + } + return npcPortal.click("Attack"); } else { return false; } @@ -339,7 +415,7 @@ private boolean attackPortals() { private boolean attackSpinner() { for (int spinner : SPINNER_IDS) { - if (Rs2Npc.interact(spinner, "attack")) { + if (Microbot.getRs2NpcCache().query().withId(spinner).interact("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); return true; } @@ -349,7 +425,7 @@ private boolean attackSpinner() { private boolean attackBrawler() { for (int brawler : BRAWLER_IDS) { - if (Rs2Npc.interact(brawler, "attack")) { + if (Microbot.getRs2NpcCache().query().withId(brawler).interact("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); return true; } @@ -357,11 +433,52 @@ private boolean attackBrawler() { return false; } + private void activateSpecialAttackIfReady() { + Optional specialAttackWeapon = getEquippedSpecialAttackWeapon(); + if (specialAttackWeapon.isEmpty() || !hasCombatTarget()) { + return; + } + + int configuredEnergyRequired = config.specialAttackPercentage() * 10; + if (configuredEnergyRequired <= 0) { + return; + } + + int energyRequired = Math.max(configuredEnergyRequired, specialAttackWeapon.get().getEnergyRequired()); + Rs2Combat.setSpecState(true, energyRequired); + } + + private Optional getEquippedSpecialAttackWeapon() { + Rs2ItemModel weapon = Rs2Equipment.get(EquipmentInventorySlot.WEAPON); + if (weapon == null || weapon.getName() == null) { + return Optional.empty(); + } + + String weaponName = weapon.getName().toLowerCase(Locale.ROOT); + return Arrays.stream(SpecialAttackWeaponEnum.values()) + .sorted(Comparator.comparingInt((SpecialAttackWeaponEnum specWeapon) -> specWeapon.getName().length()).reversed()) + .filter(specWeapon -> weaponName.contains(specWeapon.getName())) + .findFirst(); + } + + private boolean hasCombatTarget() { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null) { + return false; + } + + Actor target = player.getInteracting(); + return target != null && !target.isDead(); + }).orElse(false); + } + @Override public void shutdown() { Microbot.log("Pest control about to shutdown"); initialise = true; walkToCenter = false; + wasInPestControl = false; super.shutdown(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java b/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java index c689e64845..5793a99b4c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java @@ -13,7 +13,6 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -133,9 +132,9 @@ public boolean run() { } Set sawmillNpcs = Set.of(NpcID.POH_SAWMILL_OPP, NpcID.AUBURN_SAWMILL_OPERATOR); - var sawmillOperator = Rs2Npc.getNpcs(n -> sawmillNpcs.contains(n.getId())) - .findFirst() - .orElse(null); + var sawmillOperator = Microbot.getRs2NpcCache().query() + .where(n -> sawmillNpcs.contains(n.getId())) + .nearest(); if (sawmillOperator == null) { Microbot.showMessage("Unable to find Sawmill Operator!"); @@ -143,7 +142,7 @@ public boolean run() { return; } - Rs2Npc.interact(sawmillOperator, "Buy-plank"); + sawmillOperator.click("Buy-plank"); Microbot.status = "Buying Planks"; Rs2Dialogue.sleepUntilHasCombinationDialogue(); Rs2Dialogue.clickCombinationOption(plugin.getPlank().getDialogueOption()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java b/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java index 42037dd2aa..bc311450dd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java @@ -3,7 +3,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.pumper.PumperConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -21,7 +20,7 @@ public boolean run(PumperConfig config) { long startTime = System.currentTimeMillis(); if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(9090, "operate"); + Microbot.getRs2TileObjectCache().query().interact(9090, "operate"); sleep(50,200); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java index 25b9bca23d..30d4bab181 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.pyrefox.managers; import net.runelite.api.GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -169,7 +170,6 @@ private void _walkToPyreFox() */ private void _handleCatching() { - // Check what GameObject ID we're dealing with. if (_getTrapObjectAtTrapLocation() == null) { Microbot.log("No Trap GameObject found."); @@ -179,20 +179,15 @@ private void _handleCatching() var trap = _getTrapObjectAtTrapLocation(); if (trap == null) { - Microbot.log("No trap found."); return; } - if (!Rs2Camera.isTileOnScreen(trap)) - Rs2Camera.turnTo(trap); + if (!Rs2Camera.isTileOnScreen(trap.getLocalLocation())) + Rs2Camera.turnTo(trap.getLocalLocation()); - int trapId = _getTrapObjectAtTrapLocation().getId(); + int trapId = trap.getId(); - // 1. Check if a trap is active. - // 1a. Wait until fail / success - // i. collect/reset & re-lay - // 1b. Lay trap switch (trapId) { case PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP: @@ -200,7 +195,6 @@ private void _handleCatching() _handleSettingUpTrap(trap); break; case PyreFoxConstants.GAMEOBJECT_ROCK_TRAP: -// _log("Rock trap is set up, waiting."); break; case PyreFoxConstants.GAMEOBJECT_ROCK_FOX_CAUGHT: _log("We caught a fox!"); @@ -212,16 +206,16 @@ private void _handleCatching() } } - private void _handleSettingUpTrap(GameObject trap) + private void _handleSettingUpTrap(Rs2TileObjectModel trap) { - Rs2GameObject.interact(trap, "Set-trap"); + trap.click("Set-trap"); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); } - private void _handleFailedTrap(GameObject trap) + private void _handleFailedTrap(Rs2TileObjectModel trap) { - if (!Rs2GameObject.interact(trap, "reset")) + if (!trap.click("reset")) { _log("Did not find reset interaction."); return; @@ -230,20 +224,19 @@ private void _handleFailedTrap(GameObject trap) Rs2Player.waitForAnimation(); } - private void _handleFoxCaught(GameObject trap) + private void _handleFoxCaught(Rs2TileObjectModel trap) { - Rs2GameObject.interact(trap, "Check"); + trap.click("Check"); Rs2Player.waitForWalking(); Rs2Player.waitForXpDrop(Skill.HUNTER); } @Nullable - private GameObject _getTrapObjectAtTrapLocation() + private Rs2TileObjectModel _getTrapObjectAtTrapLocation() { - var object = Rs2GameObject.getGameObject(_getTrapObjectWorldPoint()); - if (object == null) - return null; - return object; + WorldPoint point = _getTrapObjectWorldPoint(); + if (point == null) return null; + return Microbot.getRs2TileObjectCache().query().within(point, 0).first(); } @Nullable @@ -251,7 +244,7 @@ private WorldPoint _getTrapObjectWorldPoint() { if (PyreFoxConstants.TRAP_OBJECT_POINT == null) { - var rock = Rs2GameObject.findObjectByIdAndDistance(PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP, 10); + var rock = Microbot.getRs2TileObjectCache().query().withId(PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP).within(10).nearest(); if (rock == null) { _log("No rock found"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java index 62a9e0b7da..316e66230b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java @@ -10,7 +10,6 @@ import net.runelite.client.plugins.microbot.pyrefox.PyreFoxPlugin; import net.runelite.client.plugins.microbot.pyrefox.enums.PyreFoxState; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -104,7 +103,7 @@ private PyreFoxState _getCurrentState() // causing exceptions to be thrown. when we do enter the CHOPPING_TREES state, we enter a while loop // which exits when reaching our log goal, or if our hitpoints drop below our configured min. HP. var trapPoint = PyreFoxConstants.TRAP_OBJECT_POINT; - var trap = trapPoint != null ? Rs2GameObject.getGameObject(PyreFoxConstants.TRAP_OBJECT_POINT) : null; + var trap = trapPoint != null ? Microbot.getRs2TileObjectCache().query().within(PyreFoxConstants.TRAP_OBJECT_POINT, 0).first() : null; boolean trapCaughtFox = (trap != null && trap.getId() == PyreFoxConstants.GAMEOBJECT_ROCK_FOX_CAUGHT); boolean surpassedLogCutThreshold = Rs2Inventory.count("logs") <= PyreFoxConstants.GATHER_LOGS_AT_AMOUNT; if ((!trapCaughtFox || trap == null) && surpassedLogCutThreshold && Rs2Player.distanceTo(PyreFoxConstants.PYRE_FOX_CENTER_POINT) < 60) diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java index 080bcb5ae6..af020b9985 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java @@ -5,9 +5,8 @@ import net.runelite.api.Point; import net.runelite.api.coords.LocalPoint; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.ui.overlay.OverlayLayer; @@ -20,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.Microbot.log; @@ -57,20 +55,18 @@ public Dimension render(Graphics2D graphics) { private void renderNpcs(Graphics2D graphics) { List npcs; - npcs = Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2Npc.getNpcs() - .filter(npc -> npc.getName() != null) - .collect(Collectors.toList())) + npcs = Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getRs2NpcCache().query().where(npc -> npc.getName() != null).toList()) .orElse(new ArrayList<>()); for (Rs2NpcModel npc : npcs) { - if (npc != null && npc.getCanvasTilePoly() != null) { + if (npc != null && npc.getLocalLocation() != null) { try { String text = ("Max Hit: " + Objects.requireNonNull(Rs2NpcManager.getStats(npc.getId())).getMaxHit()); - //npc.setOverheadText(text); LocalPoint lp = npc.getLocalLocation(); - Point textLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, text, npc.getLogicalHeight()); + Point textLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, text, npc.getNpc().getLogicalHeight()); if (textLocation == null) { continue; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index 325bc1f58b..b5ece9ae0d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -32,10 +32,8 @@ import net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtOverlay; import net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtScript; import net.runelite.client.plugins.microbot.util.Global; -import net.runelite.client.plugins.microbot.util.antiban.FieldUtil; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -48,7 +46,6 @@ import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.skillcalculator.skills.MagicAction; -import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.SplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.util.ImageUtil; @@ -56,16 +53,25 @@ import javax.inject.Inject; import javax.swing.*; +import javax.swing.plaf.ColorUIResource; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; -import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import static net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtScript.isInWintertodtRegion; @@ -85,17 +91,49 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.10"; + public static final String version = "1.8.13"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); - private static final AtomicReference> pluginList = new AtomicReference<>(); + private static final AtomicBoolean uiUpdateQueued = new AtomicBoolean(false); + private static final AtomicBoolean uiRestoreQueued = new AtomicBoolean(false); + private static final AtomicBoolean uiQueuedDuringSplash = new AtomicBoolean(false); + private static final AtomicBoolean uiUpdatePendingAfterRestore = new AtomicBoolean(false); + + private static final AtomicBoolean loggedMissingSwitcherOn = new AtomicBoolean(false); + private static final AtomicBoolean loggedMissingSwitcherOff = new AtomicBoolean(false); + private static final AtomicBoolean loggedThemeException = new AtomicBoolean(false); + private static final AtomicBoolean loggedRestoreException = new AtomicBoolean(false); + private static final AtomicBoolean loggedToggleReflectionException = new AtomicBoolean(false); + private static final AtomicBoolean loggedMissingUiManagerKey = new AtomicBoolean(false); + private static final AtomicBoolean loggedMicrobotPluginCacheFailure = new AtomicBoolean(false); + private static final AtomicBoolean loggedMicrobotPluginFallbackScan = new AtomicBoolean(false); + + private static final AtomicReference selfRef = new AtomicReference<>(); + private static final AtomicReference microbotPluginRef = new AtomicReference<>(); + + // Use explicit synchronization everywhere (avoid mixing synchronizedMap + synchronized blocks). + private static final Map originalToggleIcons = new WeakHashMap<>(); + private static final Map originalLabelColors = new WeakHashMap<>(); + private static final Set touchedLabels = Collections.newSetFromMap(new WeakHashMap<>()); + private static final Map originalUiManagerValues = new HashMap<>(); + private static final String[] UI_KEYS_TO_PATCH = new String[]{ + "Component.accentColor", + "ProgressBar.selectionForeground", + "ProgressBar.selectionBackground", + "Button.default.focusColor" + }; private static final int HALF_ROTATION = 1024; private static final int FULL_ROTATION = 2048; private static final int PITCH_INDEX = 0; private static final int YAW_INDEX = 1; private static final BufferedImage SWITCHER_ON_IMG = getImageFromConfigResource("switcher_on"); + private static final BufferedImage SWITCHER_OFF_IMG = getImageFromConfigResource("switcher_off"); private static final BufferedImage STAR_ON_IMG = getImageFromConfigResource("star_on"); + private static volatile Color lastAccentApplied = null; + private static volatile Color lastToggleColorApplied = null; + private static volatile ImageIcon lastToggleOnIcon = null; + private static volatile Window lastWindowPatched = null; public static InventorySetup loadoutToLoad = null; private static GameState lastGameState = GameState.UNKNOWN; private final int[] deltaCamera = new int[3]; @@ -167,28 +205,59 @@ private static BufferedImage getImageFromConfigResource(String imgName) { Class clazz = Class.forName("net.runelite.client.plugins.config.ConfigPanel"); return ImageUtil.loadImageResource(clazz, imgName.concat(".png")); } catch (Exception e) { - e.printStackTrace(); + log.debug("QoL: failed to load ConfigPanel image {}", imgName, e); return null; } } private static ImageIcon remapImage(BufferedImage image, Color color) { - if (color != null) { - BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), 2); - Graphics2D graphics = img.createGraphics(); - graphics.drawImage(image, 0, 0, null); - graphics.setColor(color); - graphics.setComposite(AlphaComposite.getInstance(10, 1)); - graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); - graphics.dispose(); - return new ImageIcon(img); - } else { + if (image == null || color == null) { return null; } + BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), 2); + Graphics2D graphics = img.createGraphics(); + graphics.drawImage(image, 0, 0, null); + graphics.setColor(color); + graphics.setComposite(AlphaComposite.getInstance(10, 1)); + graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); + graphics.dispose(); + return new ImageIcon(img); + } + + private static MicrobotPlugin findMicrobotPluginOnce() + { + MicrobotPlugin cached = microbotPluginRef.get(); + if (cached != null) + { + return cached; + } + + MicrobotPlugin found = (MicrobotPlugin) Microbot.getPluginManager().getPlugins().stream() + .filter(plugin -> plugin instanceof MicrobotPlugin) + .findAny().orElse(null); + if (found != null) + { + microbotPluginRef.compareAndSet(null, found); + } + return found; } @Override protected void startUp() throws AWTException { + selfRef.set(this); + // Cache MicrobotPlugin once (avoid scanning plugin list in steady-state UI updates). + try + { + findMicrobotPluginOnce(); + } + catch (Exception ex) + { + // Best-effort cache; updateUiElements() can still fall back if needed. + if (loggedMicrobotPluginCacheFailure.compareAndSet(false, true)) + { + log.debug("QoL: failed to cache MicrobotPlugin during startup", ex); + } + } if (overlayManager != null) { overlayManager.add(qoLOverlay); overlayManager.add(wintertodtOverlay); @@ -220,7 +289,10 @@ protected void startUp() throws AWTException { autoPrayer.run(config); keyManager.registerKeyListener(this); // pvpScript.run(config); - awaitExecutionUntil(() -> Microbot.getClientThread().invokeLater(this::updateUiElements), () -> !SplashScreen.isOpen(), 600); + uiQueuedDuringSplash.set(false); + awaitExecutionUntil(() -> queueUpdateUiElementsDuringSplash(), () -> !SplashScreen.isOpen(), 600); + // Splash closed; allow future splash-guarded queues. + uiQueuedDuringSplash.set(false); } @Override @@ -239,6 +311,56 @@ protected void shutDown() { potionManagerScript.shutdown(); autoPrayer.shutdown(); keyManager.unregisterKeyListener(this); + + // Best-effort restore of any UI theming we applied. + try + { + if (SwingUtilities.isEventDispatchThread()) + { + restoreUiElements(); + } + else + { + FutureTask restoreTask = new FutureTask<>(() -> + { + restoreUiElements(); + return null; + }); + SwingUtilities.invokeLater(restoreTask); + try + { + restoreTask.get(750, TimeUnit.MILLISECONDS); + } + catch (TimeoutException ignored) + { + log.warn("QoL: UI restore timed out during shutdown; continuing teardown."); + // Restore UIManager defaults immediately to reduce lingering accent. + if (SwingUtilities.isEventDispatchThread()) + { + restoreOriginalUiDefaultsOnly(); + } + else + { + SwingUtilities.invokeLater(QoLPlugin::restoreOriginalUiDefaultsOnly); + } + // Best-effort restore is already queued via `restoreTask`. + } + } + } + catch (Exception ex) + { + // shutdown path: avoid throwing; log once and continue teardown + if (loggedRestoreException.compareAndSet(false, true)) + { + log.warn("QoL: UI restore during shutdown failed", ex); + } + } + finally + { + // Always clear the ref so queued updates don't run post-shutdown. + selfRef.compareAndSet(this, null); + microbotPluginRef.set(null); + } } @Subscribe( @@ -248,7 +370,9 @@ public void onProfileChanged(ProfileChanged event) { log.info("Profile changed"); log.info("Updating UI elements"); // Wait for the splash screen to close before updating the UI elements - awaitExecutionUntil(() -> Microbot.getClientThread().invokeLater(this::updateUiElements), () -> !SplashScreen.isOpen(), 1000); + uiQueuedDuringSplash.set(false); + awaitExecutionUntil(() -> queueUpdateUiElementsDuringSplash(), () -> !SplashScreen.isOpen(), 1000); + uiQueuedDuringSplash.set(false); } @@ -284,7 +408,7 @@ public void onGameTick(GameTick event) { @Subscribe public void onGameStateChanged(GameStateChanged event) { if (event.getGameState() != GameState.UNKNOWN && lastGameState == GameState.UNKNOWN) { - updateUiElements(); + queueUpdateUiElements(); } if (event.getGameState() == GameState.LOGIN_SCREEN) { @@ -362,7 +486,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { event.consume(); Microbot.getClientThread().runOnSeperateThread(() -> { Rs2Inventory.fillPouches(); - Rs2GameObject.interact(ObjectID.WORKBENCH_43754); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.WORKBENCH_43754); return null; }); @@ -373,7 +497,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { event.consume(); Microbot.getClientThread().runOnSeperateThread(() -> { Rs2Inventory.fillPouches(); - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); return null; }); @@ -386,7 +510,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { Global.sleepUntil(() -> !Rs2Inventory.anyPouchFull(), () -> { Rs2Inventory.emptyPouches(); Rs2Inventory.waitForInventoryChanges(3000); - Rs2GameObject.interact("Altar"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Altar").interact()); Rs2Inventory.waitForInventoryChanges(3000); } , 10000, 200); @@ -748,73 +872,506 @@ public void updateWintertodtInterupted(boolean interupted) { * @return true if the UI elements are successfully updated, false otherwise. */ private boolean updateUiElements() { - try { - // Get the Field object for the accent color (BRAND_ORANGE) in the ColorScheme class - Field accentColorField = ColorScheme.class.getDeclaredField("BRAND_ORANGE"); - // Update the accent color with the value from the config - FieldUtil.setFinalStatic(accentColorField, config.accentColor()); - - // Get the PluginToggleButton class to access its ON_SWITCHER field - Class pluginButton = Class.forName("net.runelite.client.plugins.microbot.ui.MicrobotPluginToggleButton"); - Field onSwitcherPluginPanel = pluginButton.getDeclaredField("ON_SWITCHER"); - onSwitcherPluginPanel.setAccessible(true); - // Update the ON_SWITCHER field with a remapped image based on the config toggle button color - FieldUtil.setFinalStatic(onSwitcherPluginPanel, remapImage(SWITCHER_ON_IMG, config.toggleButtonColor())); + // Swing/FlatLaf layout is not thread-safe; enforce EDT execution. + if (!SwingUtilities.isEventDispatchThread()) { + QoLPlugin self = selfRef.get(); + if (self == null) + { + return false; + } + // If a run is already queued and we still have a live plugin, consider it pending. + if (uiUpdateQueued.get()) + { + return true; + } + return queueUpdateUiElements(); + } + try { // Find the ConfigPlugin instance from the plugin manager - MicrobotPlugin microbotPlugin = (MicrobotPlugin) Microbot.getPluginManager().getPlugins().stream() - .filter(plugin -> plugin instanceof MicrobotPlugin) - .findAny().orElse(null); + MicrobotPlugin microbotPlugin = findMicrobotPluginOnce(); + if (microbotPlugin == null && loggedMicrobotPluginFallbackScan.compareAndSet(false, true)) + { + log.debug("QoL: MicrobotPlugin ref was empty; fell back to plugin manager scan."); + } // If ConfigPlugin is not found, log an error and return false if (microbotPlugin == null) { Microbot.log("Config Plugin not found"); return false; } + microbotPluginRef.set(microbotPlugin); // Get the plugin list panel from the ConfigPlugin instance JPanel pluginListPanel = getPluginListPanel(microbotPlugin); + Window pluginWindow = SwingUtilities.getWindowAncestor(pluginListPanel); + if (pluginWindow != lastWindowPatched) + { + // If the config window closed while we had an accent applied, restore defaults now. + if (pluginWindow == null && lastAccentApplied != null) + { + restoreOriginalUiDefaultsOnly(); + lastAccentApplied = null; + } + lastWindowPatched = pluginWindow; + synchronized (originalToggleIcons) + { + originalToggleIcons.clear(); + } + synchronized (originalLabelColors) + { + originalLabelColors.clear(); + } + synchronized (touchedLabels) + { + touchedLabels.clear(); + } + lastAccentApplied = null; + lastToggleColorApplied = null; + lastToggleOnIcon = null; + } + + // Best-effort accent color behavior (no static-final mutation / no Unsafe). + try + { + Color accent = config.accentColor(); + if (accent != null) + { + ColorUIResource accentRes = new ColorUIResource(accent); + rememberOriginalUiDefaults(); + // Only apply+refresh when accent actually changed. + if (!accent.equals(lastAccentApplied)) + { + UIManager.put("Component.accentColor", accentRes); + UIManager.put("ProgressBar.selectionForeground", accentRes); + UIManager.put("ProgressBar.selectionBackground", accentRes); + UIManager.put("Button.default.focusColor", accentRes); + lastAccentApplied = accent; + + // Refresh UI tree to apply defaults. + try + { + if (pluginWindow != null) + { + SwingUtilities.updateComponentTreeUI(pluginWindow); + pluginWindow.invalidate(); + pluginWindow.validate(); + pluginWindow.repaint(); + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI refresh after accent update failed", ex); + } + } + } + } + else if (lastAccentApplied != null) + { + // Accent cleared; restore original defaults (if we captured them) and refresh UI. + restoreOriginalUiDefaultsOnly(); + lastAccentApplied = null; + + try + { + if (pluginWindow != null) + { + SwingUtilities.updateComponentTreeUI(pluginWindow); + pluginWindow.invalidate(); + pluginWindow.validate(); + pluginWindow.repaint(); + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI refresh after accent restore failed", ex); + } + } + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI theme update failed", ex); + } + } + // Set the plugin list using the retrieved plugin list panel - pluginList.set(getPluginList(pluginListPanel)); + List currentPluginList = getPluginList(pluginListPanel); // If the plugin list is still null, log an error and return false - if (pluginList.get() == null) { + if (currentPluginList == null) { Microbot.log("Plugin list is null, waiting for it to be initialized"); return false; } - // Iterate through each plugin in the plugin list - for (Object plugin : pluginList.get()) { + // Pass 1: capture originals (before applying any changes). + for (Object plugin : currentPluginList) + { + try + { + if (plugin instanceof JPanel) + { + for (Component component : ((JPanel) plugin).getComponents()) + { + if (component instanceof JLabel) + { + JLabel label = (JLabel) component; + synchronized (originalLabelColors) + { + originalLabelColors.computeIfAbsent(label, l -> l.getForeground()); + } + } + } + } + + JToggleButton onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); + if (onOffToggle == null) + { + continue; + } + synchronized (originalToggleIcons) + { + originalToggleIcons.computeIfAbsent(onOffToggle, t -> new IconState(t.getIcon(), t.getSelectedIcon())); + } + } + catch (Exception ex) + { + // Best-effort: one broken row shouldn't abort the whole update. + if (loggedToggleReflectionException.compareAndSet(false, true)) + { + log.debug("QoL: reflection lookup for plugin toggle failed; UI theming may be partial.", ex); + } + } + } + + // Pass 2: apply theming changes. + final Color labelColor = config.pluginLabelColor(); + final ImageIcon onIcon = getCachedToggleOnIcon(config.toggleButtonColor()); + for (Object plugin : currentPluginList) { // If the plugin is a JPanel, update the color of any JLabel components within it if (plugin instanceof JPanel) { for (Component component : ((JPanel) plugin).getComponents()) { if (component instanceof JLabel) { + JLabel label = (JLabel) component; // Set the label color based on the config - component.setForeground(config.pluginLabelColor()); + if (labelColor != null) + { + if (!labelColor.equals(label.getForeground())) + { + synchronized (touchedLabels) + { + touchedLabels.add(label); + } + label.setForeground(labelColor); + } + } } } } // Get the on/off toggle button for the plugin and update its selected icon - JToggleButton onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); - onOffToggle.setSelectedIcon(remapImage(SWITCHER_ON_IMG, config.toggleButtonColor())); + JToggleButton onOffToggle; + try + { + onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); + if (onOffToggle == null) + { + continue; + } + } + catch (Exception ex) + { + continue; + } + // Only recolor the "ON" (selected) icon. Do not overwrite the "OFF" icon, + // otherwise disabled plugins will also appear enabled. + Icon offIcon = onOffToggle.getIcon(); + if (onIcon == null) + { + if (loggedMissingSwitcherOn.compareAndSet(false, true)) + { + log.warn("QoL: missing ConfigPanel switcher_on.png; leaving plugin toggle icons unchanged."); + } + continue; + } + onOffToggle.setSelectedIcon(onIcon); + if (offIcon != null) { + onOffToggle.setIcon(offIcon); + } else if (SWITCHER_OFF_IMG != null) { + // Fallback: ensure OFF icon is distinct if missing. + onOffToggle.setIcon(new ImageIcon(SWITCHER_OFF_IMG)); + } else { + if (loggedMissingSwitcherOff.compareAndSet(false, true)) + { + log.warn("QoL: missing ConfigPanel switcher_off.png; OFF icon fallback unavailable."); + } + } } return true; } catch (Exception e) { // Log any exceptions that occur during the UI update process String errorMessage = "QoL Error updating UI elements: " + e.getMessage(); - log.error(errorMessage); + log.error(errorMessage, e); Microbot.log(errorMessage); return false; } } + private static ImageIcon getCachedToggleOnIcon(Color toggleColor) + { + if (SWITCHER_ON_IMG == null) + { + return null; + } + + if (toggleColor == null) + { + lastToggleColorApplied = null; + lastToggleOnIcon = null; + return null; + } + + if (!toggleColor.equals(lastToggleColorApplied) || lastToggleOnIcon == null) + { + lastToggleOnIcon = remapImage(SWITCHER_ON_IMG, toggleColor); + lastToggleColorApplied = toggleColor; + } + + return lastToggleOnIcon; + } + + private static final class IconState + { + private final Icon icon; + private final Icon selectedIcon; + + private IconState(Icon icon, Icon selectedIcon) + { + this.icon = icon; + this.selectedIcon = selectedIcon; + } + } + + private static void rememberOriginalUiDefaults() + { + synchronized (originalUiManagerValues) + { + if (!originalUiManagerValues.isEmpty()) + { + return; + } + + for (String k : UI_KEYS_TO_PATCH) + { + Object v = UIManager.get(k); + originalUiManagerValues.put(k, v); + if (v == null && !UIManager.getDefaults().containsKey(k) && loggedMissingUiManagerKey.compareAndSet(false, true)) + { + log.debug("QoL: UIManager key '{}' not present in defaults; accent patch may be FlatLaf-version dependent.", k); + } + } + } + } + + private static void restoreOriginalUiDefaultsOnly() + { + synchronized (originalUiManagerValues) + { + if (originalUiManagerValues.isEmpty()) + { + return; + } + + for (String k : UI_KEYS_TO_PATCH) + { + UIManager.put(k, originalUiManagerValues.get(k)); + } + } + } + + private static void queueRestoreUiElements() + { + if (!uiRestoreQueued.compareAndSet(false, true)) + { + return; + } + + SwingUtilities.invokeLater(() -> + { + try + { + restoreUiElements(); + } + finally + { + uiRestoreQueued.set(false); + } + }); + } + + private static void restoreUiElements() + { + if (!SwingUtilities.isEventDispatchThread()) + { + log.warn("QoL: restoreUiElements called off-EDT; skipping."); + return; + } + + // Restore UIManager defaults. + synchronized (originalUiManagerValues) + { + if (!originalUiManagerValues.isEmpty()) + { + for (String k : UI_KEYS_TO_PATCH) + { + UIManager.put(k, originalUiManagerValues.get(k)); + } + } + } + + // Restore per-component UI state (best-effort; components may be gone/rebuilt). + synchronized (originalToggleIcons) + { + for (Map.Entry e : originalToggleIcons.entrySet()) + { + JToggleButton t = e.getKey(); + IconState s = e.getValue(); + if (t != null && s != null) + { + t.setIcon(s.icon); + t.setSelectedIcon(s.selectedIcon); + } + } + } + + synchronized (originalLabelColors) + { + for (Map.Entry e : originalLabelColors.entrySet()) + { + JLabel l = e.getKey(); + boolean touched; + synchronized (touchedLabels) + { + touched = touchedLabels.contains(l); + } + if (l != null && touched) + { + l.setForeground(e.getValue()); + } + } + } + + // Refresh UI tree to apply restored defaults. + try + { + QoLPlugin self = selfRef.get(); + MicrobotPlugin microbotPlugin = microbotPluginRef.get(); + if (self != null && microbotPlugin != null) + { + JPanel pluginListPanel = self.getPluginListPanel(microbotPlugin); + Window w = SwingUtilities.getWindowAncestor(pluginListPanel); + if (w != null) + { + SwingUtilities.updateComponentTreeUI(w); + w.invalidate(); + w.validate(); + w.repaint(); + } + } + } + catch (Exception ex) + { + if (loggedRestoreException.compareAndSet(false, true)) + { + log.warn("QoL: UI restore refresh failed", ex); + } + } + + // Clear caches so re-enable re-captures fresh state. + synchronized (originalToggleIcons) + { + originalToggleIcons.clear(); + } + synchronized (originalLabelColors) + { + originalLabelColors.clear(); + } + synchronized (touchedLabels) + { + touchedLabels.clear(); + } + // Intentionally keep captured defaults for plugin lifetime so we can restore later. + lastAccentApplied = null; + lastToggleColorApplied = null; + lastToggleOnIcon = null; + lastWindowPatched = null; + + // If an update was requested while restore was in-progress, run it now. + if (uiUpdatePendingAfterRestore.compareAndSet(true, false)) + { + queueUpdateUiElements(); + } + } + + private static boolean queueUpdateUiElements() + { + // Don't interleave apply with restore. + if (uiRestoreQueued.get()) + { + uiUpdatePendingAfterRestore.set(true); + return false; + } + + // Prevent re-entrant scheduling during layout/validate cascades. + if (!uiUpdateQueued.compareAndSet(false, true)) + { + return selfRef.get() != null; + } + + SwingUtilities.invokeLater(() -> + { + try + { + QoLPlugin self = selfRef.get(); + if (self != null) + { + self.updateUiElements(); + } + } + finally + { + uiUpdateQueued.set(false); + } + }); + + return true; + } + + private static boolean queueUpdateUiElementsDuringSplash() + { + // Prevent repeated enqueue spam while waiting for splash to close. + if (!uiQueuedDuringSplash.compareAndSet(false, true)) + { + return false; + } + return queueUpdateUiElements(); + } + private JPanel getPluginListPanel(MicrobotPlugin microbotPlugin) throws ClassNotFoundException { Class pluginListPanelClass = Class.forName("net.runelite.client.plugins.microbot.ui.MicrobotPluginListPanel"); - assert microbotPlugin != null; + if (microbotPlugin == null) + { + throw new IllegalStateException("MicrobotPlugin instance is null"); + } return (JPanel) microbotPlugin.getInjector().getProvider(pluginListPanelClass).get(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java index 3ff368eae9..80600ba921 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java @@ -4,13 +4,11 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.qualityoflife.QoLConfig; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; public class SpecialAttackScript extends Script { @@ -23,9 +21,13 @@ public boolean run(QoLConfig config) { if (!config.useSpecWeapon()) return; if (Rs2Equipment.all("guthan's").count() == 4) return; if (Rs2Player.isInteracting()) { - npc.set((Rs2NpcModel) Rs2Player.getInteracting()); - if (Microbot.getSpecialAttackConfigs().useSpecWeapon()) { - Rs2Npc.attack(npc.get()); + var interacting = Rs2Player.getInteracting(); + if (interacting instanceof net.runelite.api.NPC) { + var targetNpc = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(interacting)).nearest(); + npc.set(targetNpc); + } + if (npc.get() != null && Microbot.getSpecialAttackConfigs().useSpecWeapon()) { + npc.get().click("Attack"); } } } catch (Exception ex) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java index 370683f8e7..787c9a9590 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java @@ -1,7 +1,5 @@ package net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; @@ -10,17 +8,16 @@ import net.runelite.api.gameval.ObjectID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.qualityoflife.QoLConfig; import net.runelite.client.plugins.microbot.qualityoflife.QoLPlugin; import net.runelite.client.plugins.microbot.qualityoflife.enums.WintertodtActions; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -32,8 +29,8 @@ public class WintertodtScript extends Script { public static QoLConfig config; - public static GameObject unlitBrazier; - public static GameObject brokenBrazier; + public static Rs2TileObjectModel unlitBrazier; + public static Rs2TileObjectModel brokenBrazier; public static Rs2NpcModel pyromancer; public static Rs2NpcModel incapitatedPyromancer; public static boolean helpedIncapitatedPyromancer = false; @@ -43,7 +40,8 @@ public class WintertodtScript extends Script { private QoLPlugin qolPlugin; public static boolean isInWintertodtRegion() { - return Rs2Player.getWorldLocation().getRegionID() == 6462; + var location = Rs2Player.getWorldLocation(); + return location != null && location.getRegionID() == 6462; } public boolean run(QoLConfig config) { @@ -62,8 +60,8 @@ public boolean run(QoLConfig config) { } else { wintertodtHp = -1; } - brokenBrazier = Rs2GameObject.getGameObject(obj -> obj.getId() == ObjectID.WINT_BRAZIER_BROKEN, 5); - unlitBrazier = Rs2GameObject.getGameObject(obj -> obj.getId() == ObjectID.WINT_BRAZIER, 5); + brokenBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER_BROKEN).within(5).nearest(); + unlitBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER).within(5).nearest(); shouldEat(); @@ -72,7 +70,7 @@ public boolean run(QoLConfig config) { NewMenuEntry actionToResume = config.wintertodtActions().getMenuEntry(); if (config.wintertodtActions().equals(WintertodtActions.FEED)) { - GameObject fireBrazier = Rs2GameObject.getGameObject(ObjectID.WINT_BRAZIER_LIT); + Rs2TileObjectModel fireBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER_LIT).nearest(); if (fireBrazier != null && fireBrazier.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) < 5) { if (!Rs2Inventory.contains(ItemID.WINT_BRUMA_ROOT) && !Rs2Inventory.contains(ItemID.WINT_BRUMA_KINDLING)) { qolPlugin.updateLastWinthertodtAction(WintertodtActions.NONE); @@ -112,14 +110,14 @@ public void shutdown() { public void onNpcChanged(NpcChanged event) { if (event.getNpc().getId() == 7372) { - new Rs2NpcModel(event.getNpc()); + Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } else if (event.getNpc().getId() == 7371) { - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); incapitatedPyromancer = null; if (helpedIncapitatedPyromancer) { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -131,29 +129,29 @@ public void onNpcSpawned(NpcSpawned event) { if (event.getNpc().getId() == 7372) { if (incapitatedPyromancer != null) { if (incapitatedPyromancer.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) > event.getNpc().getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation())) { - incapitatedPyromancer = new Rs2NpcModel(event.getNpc()); + incapitatedPyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); return; } } - incapitatedPyromancer = new Rs2NpcModel(event.getNpc()); + incapitatedPyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } if (event.getNpc().getId() == 7371) { if (pyromancer != null) { if (pyromancer.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) > event.getNpc().getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation())) { - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); return; } } - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } } public void onNpcDespawned(NpcDespawned event) { - if (event.getNpc().equals(incapitatedPyromancer)) { - incapitatedPyromancer = Rs2Npc.getNpc("Incapacitated Pyromancer"); + if (incapitatedPyromancer != null && event.getNpc().equals(incapitatedPyromancer.getNpc())) { + incapitatedPyromancer = Microbot.getRs2NpcCache().query().withName("Incapacitated Pyromancer").nearestOnClientThread(); } - if (event.getNpc().equals(pyromancer)) { - pyromancer = Rs2Npc.getNpc("Pyromancer"); + if (pyromancer != null && event.getNpc().equals(pyromancer.getNpc())) { + pyromancer = Microbot.getRs2NpcCache().query().withName("Pyromancer").nearestOnClientThread(); } } @@ -162,7 +160,7 @@ public void onChatMessage(ChatMessage chatMessage) { if (message.contains("The brazier is broken and shrapnel")) { if (config.fixBrokenBrazier()) { qolPlugin.updateWintertodtInterupted(false); - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(brokenBrazier, "Fix"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> brokenBrazier.click("Fix"), 300, TimeUnit.MILLISECONDS); } } @@ -170,12 +168,12 @@ public void onChatMessage(ChatMessage chatMessage) { if (incapitatedPyromancer != null) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } else { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -185,12 +183,12 @@ public void onChatMessage(ChatMessage chatMessage) { if (incapitatedPyromancer != null) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } else { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -200,7 +198,7 @@ public void onChatMessage(ChatMessage chatMessage) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java index 4fde9d89bd..9da25bdf09 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class revKillerPlugin extends Plugin { - public static final String version = "2.0.8"; + public static final String version = "2.0.9"; @Inject private net.runelite.client.plugins.microbot.revkiller.revKillerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java index 5910d34743..f758823166 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java @@ -31,8 +31,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.player.Rs2Pvp; @@ -128,7 +127,7 @@ public boolean run(revKillerConfig config) { if(firstRun || weDied) { Microbot.log("It's our first run or we died!"); - if(firstRun && Rs2Npc.getNpc(config.selectedRev().getName()) != null){ + if(firstRun && Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearestOnClientThread() != null){ // we're all ready geared and there firstRun = false; Microbot.log("It's our first run and we're all ready here!"); @@ -270,7 +269,7 @@ public boolean goodLootOnGround(){ } public Rs2NpcModel revenentKnight() { - return Rs2Npc.getNpc(7939); + return Microbot.getRs2NpcCache().query().withId(7939).nearest(); } public void kiteTheKnight(){ @@ -295,7 +294,7 @@ public void kiteTheKnight(){ } else { Microbot.log("We need to click the rev."); if(revenentKnight()!=null && revenentKnight().getWorldLocation().distanceTo(jammedTile)<=2) { - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); return; } @@ -313,7 +312,7 @@ public void kiteTheKnight(){ sleepUntil(() -> Rs2Player.isMoving(), Rs2Random.between(1000, 3000)); sleepUntil(() -> !Rs2Player.isMoving(), Rs2Random.between(2000, 3000)); } - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); return; } @@ -350,7 +349,7 @@ public void kiteTheKnight(){ if(playerCheck()){return;} if(revenentKnight() == null) return; - if(Rs2Npc.interact(revenentKnight(), "Attack")){ + if(revenentKnight().click("Attack")){ Microbot.log("We attacked the knight"); sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(1000,3000)); sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(2000,3000)); @@ -419,7 +418,7 @@ public void kiteTheKnight(){ } if(Rs2Player.getWorldLocation().equals(fifthTile)) { - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); Microbot.log("Rev should be locked"); } @@ -501,7 +500,7 @@ public void WalkToRevs(){ } else { if(!Rs2Dialogue.isInDialogue()){ Microbot.log("At the cave, clicking."); - if(Microbot.getRs2TileObjectCache().query().withId(31555).nearest().click("Enter")){ + if(Microbot.getRs2TileObjectCache().query().interact(31555, "Enter")){ sleepUntil(()-> Rs2Dialogue.isInDialogue(), generateRandomNumber(1000,3000)); } } @@ -752,7 +751,7 @@ public void enablePrayer(){ } public void fightrev(revKillerConfig config){ - Rs2NpcModel Rev = Rs2Npc.getNpc(config.selectedRev().getName()); + Rs2NpcModel Rev = Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearest(); if(Rev!=null){ @@ -765,7 +764,7 @@ public void fightrev(revKillerConfig config){ Microbot.log("Attacking Rev"); - if (Rs2Npc.interact(Rev, "Attack")) { + if (Rev.click("Attack")) { sleepUntil(() -> Rev.isDead() || !Rs2Player.isInCombat() || isItTimeToGo() || Rs2Player.getHealthPercentage() <= generateRandomNumber(70, 80), generateRandomNumber(60000, 120000)); hoppedWorld=false; } @@ -774,7 +773,7 @@ public void fightrev(revKillerConfig config){ if(Rev.isInteracting()) { if(hoppedWorld) { Microbot.log("Rev is attacking us attacking back."); - if (Rs2Npc.interact(Rev, "Attack")) { + if (Rev.click("Attack")) { hoppedWorld=false; sleepUntil(() -> Rev.isDead() || !Rs2Player.isInCombat() || isItTimeToGo() || Rs2Player.getHealthPercentage() <= generateRandomNumber(70, 80), generateRandomNumber(60000, 120000)); } @@ -1002,8 +1001,7 @@ public void loot(){ } } if(!Rs2Inventory.isFull()){ - if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest() != null){ - Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest().click("Take"); + if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).interact("Take")){ Rs2Inventory.waitForInventoryChanges(Rs2Random.between(4000,6000)); } } @@ -1019,8 +1017,7 @@ public void loot(){ } } if(!Rs2Inventory.isFull()){ - if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest() != null){ - Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest().click("Take"); + if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).interact("Take")){ Rs2Inventory.waitForInventoryChanges(Rs2Random.between(4000,6000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java b/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java index 34640f43f0..3da4c3edd8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java @@ -11,7 +11,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -205,7 +204,7 @@ private boolean clickObstacle() { Obstacles.Obstacle nextObstacle = OBSTACLES[closestIndex + 1]; if (closestIndex == 0 && !Rs2Player.getWorldLocation().equals(currentObstacle.getTile())) { - Rs2GameObject.interact(currentObstacle.getObjectId()); + Microbot.getRs2TileObjectCache().query().interact(currentObstacle.getObjectId()); sleepUntil(() -> Rs2Player.getWorldLocation().equals(currentObstacle.getTile())); return true; } @@ -243,7 +242,7 @@ private boolean useFlashPowder() { private boolean useTileObject() { if (Rs2Inventory.hasItem(ItemID.ROGUESDEN_PUZZLE_MOSAIC_TILE1)) { Microbot.log("Handle tile door"); - Rs2GameObject.interact(7234, "Open"); + Microbot.getRs2TileObjectCache().query().interact(7234, "Open"); Rs2Widget.sleepUntilHasWidget("Select"); Rs2Widget.clickWidget("Select"); Rs2Inventory.waitForInventoryChanges(3000); @@ -323,7 +322,7 @@ private boolean useEnergyPotions() { private void enterMinigame() { WalkerState state = Rs2Walker.walkWithState(new WorldPoint(3056, 4991, 1)); if (state == WalkerState.ARRIVED) { - Rs2GameObject.interact(ObjectID.ROGUESDEN_MAZEENTRANCE); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.ROGUESDEN_MAZEENTRANCE); sleepUntil(() -> Rs2Inventory.hasItem(ItemID.ROGUESDEN_GEM)); } } @@ -344,9 +343,9 @@ private void handleObstacle(Obstacles.Obstacle obstacle) { if (obstacle.getObjectId() != -1) { if (obstacle.getObjectId() == 7249) { - Rs2GameObject.interact(obstacle.getObjectId(), "search"); // Handles wall searching + Microbot.getRs2TileObjectCache().query().interact(obstacle.getObjectId(), "search"); // Handles wall searching } else { - Rs2GameObject.interact(obstacle.getObjectId()); + Microbot.getRs2TileObjectCache().query().interact(obstacle.getObjectId()); } Rs2Player.waitForWalking(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java index 2bd54dadcd..2028e16c13 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java @@ -7,6 +7,7 @@ import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.sailing.features.salvaging.SalvagingHighlight; +import net.runelite.client.plugins.microbot.sailing.features.salvaging.SalvagingScript; import net.runelite.client.plugins.microbot.sailing.features.trials.TrialsScript; import net.runelite.client.plugins.microbot.sailing.features.trials.debug.BoatPathOverlay; import net.runelite.client.plugins.microbot.sailing.features.trials.overlay.TrialRouteOverlay; @@ -30,7 +31,7 @@ @Slf4j public class MSailingPlugin extends Plugin { - static final String version = "2.1.0"; + static final String version = "2.2.56"; @Inject private SailingConfig config; @@ -46,6 +47,9 @@ SailingConfig provideConfig(ConfigManager configManager) { @Inject private SalvagingHighlight salvagingHighlight; + @Inject + private SalvagingScript salvagingScript; + @Inject private SailingScript sailingScript; @Inject @@ -63,6 +67,7 @@ protected void startUp() throws AWTException { overlayManager.add(boatPathOverlay); overlayManager.add(trialRouteOverlay); } + salvagingScript.register(); trialsScript.register(); sailingScript.run(); } @@ -70,6 +75,7 @@ protected void startUp() throws AWTException { protected void shutDown() { sailingScript.shutdown(); trialsScript.shutdown(); + salvagingScript.unregister(); trialsScript.unregister(); overlayManager.remove(sailingOverlay); overlayManager.remove(salvagingHighlight); diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java index 9cd3d23aef..d6f21737b3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java @@ -83,11 +83,23 @@ default boolean openCaskets() return false; } + @ConfigItem( + keyName = "useCargoHold", + name = "Use Cargo Hold", + description = "When salvaging, open the cargo hold and use Deposit inventory, then read the hold grid for fullness. When the hold is full or nearly full, the script withdraws and processes salvage (alch/drop/caskets) until salvage stacks in the hold reach zero.", + position = 3, + section = generalSection + ) + default boolean useCargoHold() + { + return false; + } + @ConfigItem( keyName = "alchOrder", name = "Alch Order", description = "Order in which to high alch items. LIST_ORDER follows your alch list. LEFT_TO_RIGHT sweeps row by row. RIGHT_TO_LEFT sweeps rows right to left. TOP_TO_BOTTOM sweeps column by column. BOTTOM_TO_TOP sweeps columns bottom to top.", - position = 3, + position = 4, section = generalSection ) default AlchOrder alchOrder() @@ -99,7 +111,7 @@ default AlchOrder alchOrder() keyName = "dropItems", name = "Drop items", description = "Comma-separated list of items to drop when salvaging.", - position = 4, + position = 5, section = generalSection ) default String dropItems() diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java new file mode 100644 index 0000000000..b07547c96b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +/** + * Widget ids for the sailing cargo-hold interface not exposed on {@link net.runelite.api.gameval.InterfaceID}. + */ +public final class CargoHoldInterfaceWidgets { + + private CargoHoldInterfaceWidgets() { + } + + /** + * Occupied-slot count text while the hold is open ({@code client.getWidget(943, 4)}). In practice this child often + * shows only {@code X} (occupied slots), not a full {@code X / N} line; we still parse the first run of digits from + * {@link net.runelite.api.widgets.Widget#getText()} so either shape works. + */ + public static final int CARGO_HOLD_OCCUPIED_TEXT_GROUP = 943; + + public static final int CARGO_HOLD_OCCUPIED_TEXT_CHILD = 4; +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java new file mode 100644 index 0000000000..4de6acb09d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java @@ -0,0 +1,87 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +import com.google.common.collect.ImmutableMap; +import net.runelite.api.gameval.ObjectID1; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Cargo hold tile object IDs and capacities per wood tier and boat layout (raft / 2x5 skiff / large sloop). + * {@link ObjectID1} defines three visuals per tier: {@code SAILING_BOAT_CARGO_HOLD__}, + * {@code ..._NO_CARGO}, and {@code ..._CARGO}; all map to the same slot capacity. + */ +public final class CargoHoldObjectIds { + + private CargoHoldObjectIds() { + } + + /** + * Every cargo hold object ID (base, no-cargo, and cargo visuals) mapped to maximum slots for that + * boat tier. Used occupancy is read from the open cargo-hold interface item grid (widgets) after opening the hold. + */ + public static final Map ID_TO_CAPACITY = buildIdToCapacity(); + + public static final Set ALL_IDS = Collections.unmodifiableSet(ID_TO_CAPACITY.keySet()); + + private static Map buildIdToCapacity() { + ImmutableMap.Builder b = ImmutableMap.builder(); + // Basic (Regular) — raft 20, skiff (2x5) 30, sloop (large) 40 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT_CARGO, 20); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5_CARGO, 30); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE_CARGO, 40); + // Oak — 30, 45, 60 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT_CARGO, 30); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5_CARGO, 45); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE_CARGO, 60); + // Teak — 45, 60, 90 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT_CARGO, 45); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5_CARGO, 60); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE_CARGO, 90); + // Mahogany — 60, 90, 120 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT_CARGO, 60); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5_CARGO, 90); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE_CARGO, 120); + // Camphor — 80, 120, 160 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT_CARGO, 80); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5_CARGO, 120); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE_CARGO, 160); + // Ironwood — 105, 150, 210 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT_CARGO, 105); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5_CARGO, 150); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE_CARGO, 210); + // Rosewood — 120, 180, 240 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT_CARGO, 120); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5_CARGO, 180); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE_CARGO, 240); + return b.build(); + } + + private static void putTier(ImmutableMap.Builder b, int baseId, int noCargoId, int cargoId, int capacity) { + b.put(baseId, capacity); + b.put(noCargoId, capacity); + b.put(cargoId, capacity); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java index 6a7bdfaaf7..56ecd4cf51 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java @@ -14,6 +14,11 @@ import javax.inject.Inject; import java.awt.*; +/** + * Renders the same salvage-area outline as the RuneLite Plugin Hub "Sailing" plugin + * ({@code com.duckblade.osrs.sailing.features.salvaging.SalvagingHighlight}): {@link Perspective#getCanvasTileAreaPoly} + * with a fixed tile radius. Wreck lists come from {@link SalvagingScript} (top-level scene scan + spawn sync). + */ @Slf4j public class SalvagingHighlight extends Overlay { @@ -38,7 +43,7 @@ public Dimension render(Graphics2D graphics) { return null; } - int sailingLevel = client.getBoostedSkillLevel(Skill.SAILING); + int sailingLevel = safeBoostedSailingLevel(client); for (var wreck : salvagingScript.getActiveWrecks()) { Integer levelReq = SalvageObjectIds.SALVAGE_LEVEL_REQ.get(wreck.getId()); @@ -67,6 +72,15 @@ public Dimension render(Graphics2D graphics) { return null; } + /** When the client's skill level buffer is too short, {@link Client#getBoostedSkillLevel(Skill)} can throw. */ + private static int safeBoostedSailingLevel(Client client) { + try { + return client.getBoostedSkillLevel(Skill.SAILING); + } catch (RuntimeException ex) { + return 99; + } + } + private void renderWreck(Graphics2D graphics, Rs2TileObjectModel wreck, Color colour) { Polygon poly = Perspective.getCanvasTileAreaPoly(client, wreck.getLocalLocation(), SIZE_SALVAGEABLE_AREA); if (poly != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java index c8b02a7134..b2d967df11 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java @@ -1,79 +1,298 @@ package net.runelite.client.plugins.microbot.sailing.features.salvaging; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.DecorativeObject; +import net.runelite.api.GameObject; +import net.runelite.api.Player; +import net.runelite.api.Scene; import net.runelite.api.Skill; +import net.runelite.api.Tile; +import net.runelite.api.TileObject; +import net.runelite.api.WorldView; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.widgets.Widget; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.api.boat.Rs2BoatCache; import net.runelite.client.plugins.microbot.api.player.models.Rs2PlayerModel; import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.sailing.AlchOrder; import net.runelite.client.plugins.microbot.sailing.SailingConfig; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.util.Text; import javax.inject.Inject; +import javax.inject.Singleton; +import java.awt.event.KeyEvent; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static net.runelite.client.plugins.microbot.util.Global.sleep; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j +@Singleton public class SalvagingScript { + /** First decimal number in the occupied widget text (often just {@code X}; still works if the client shows {@code X / N}). */ + private static final Pattern CARGO_HOLD_FIRST_NUMBER = Pattern.compile("(\\d+)"); + private static final int SIZE_SALVAGEABLE_AREA = 15; private static final int MIN_INVENTORY_FULL = 24; - private static final int MAX_INVENTORY_FULL = 28; private static final int SALVAGE_TIMEOUT = 20000; private static final int DEPLOY_TIMEOUT = 5000; private static final int WAIT_TIME = 5000; private static final int WAIT_TIME_MAX = 10000; - + private static final int CARGO_HOLD_UI_TIMEOUT_MS = 8000; + private static final int CARGO_HOLD_WITHDRAW_FAIL_THRESHOLD = 5; + private static final int CARGO_HOLD_WITHDRAW_NO_GAIN_THRESHOLD = 5; + /** Wait for inventory to reflect the withdraw after the salvage slot is clicked. */ + private static final int CARGO_HOLD_WITHDRAW_INVENTORY_TIMEOUT_MS = 12000; + /** Pause after clicking the salvage slot so the client can apply the withdraw before inventory polling or Escape. */ + private static final int CARGO_HOLD_POST_WITHDRAW_CLICK_MIN_MS = 2200; + private static final int CARGO_HOLD_POST_WITHDRAW_CLICK_MAX_MS = 5000; + /** After salvage appears in inventory, wait again before Escape so the hold does not close mid-pipeline. */ + private static final int CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MIN_MS = 800; + private static final int CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MAX_MS = 2200; + /** Periodically re-open the hold and count ITEMS widgets so "full" stays accurate without item-container tracking. */ + private static final int CARGO_HOLD_WIDGET_RESYNC_MIN_MS = 4500; + /** After Deposit inventory, occupied text can lag; retry read while the panel stays open before Escape. */ + private static final int CARGO_HOLD_POST_DEPOSIT_READ_ATTEMPTS = 6; + private static final int CARGO_HOLD_POST_DEPOSIT_READ_GAP_MIN_MS = 120; + private static final int CARGO_HOLD_POST_DEPOSIT_READ_GAP_MAX_MS = 320; + /** Radius for {@link Rs2GameObject} scans and name fallbacks around the player (boat nested view, port, etc.). */ + private static final int NEARBY_TILE_OBJECT_SCAN_RADIUS = 32; + /** + * In-game message when deposit fails because every slot is taken ({@code Text.standardize} comparison, so extra + * punctuation or wording after this phrase still matches). + */ + private static final String CARGO_HOLD_FULL_MESSAGE_CONTAINS = "the cargo hold is full"; private final Rs2TileObjectCache tileObjectCache; + @SuppressWarnings("unused") private final Rs2BoatCache boatCache; + private final EventBus eventBus; + + /** + * Shipwreck lists rebuilt each {@link GameTick} on the client thread by scanning {@link Client#getTopLevelWorldView()} + * (the sea layer). Plugin Hub "Sailing" uses game object spawn/despawn events instead; both target the same + * {@link GameObject} ids. At-sea wrecks live on the top-level sea scene; boat facilities (e.g. cargo hold) live in the + * boarded boat's nested {@link WorldView}. {@link Rs2GameObject} / tile cache can miss one or the other depending + * on context; shipwrecks use an explicit top-level scene walk, cargo hold uses the local player world view scene walk. + */ + private final Map activeWreckByKey = new HashMap<>(); + private final Map inactiveWreckByKey = new HashMap<>(); + private volatile List activeWreckSnapshot = List.of(); + private volatile List inactiveWreckSnapshot = List.of(); + + /** Max cargo slots for this boat tier ({@link CargoHoldObjectIds#ID_TO_CAPACITY}). */ + private int cargoHoldCapacity = -1; + /** Occupied slots: parsed from {@link CargoHoldInterfaceWidgets} occupied text (usually just {@code X}) when the hold is open; else ITEMS grid count. */ + private volatile int cargoHoldCount = -1; + /** Salvage stacks in the hold from the same grid (item name contains "salvage"; one slot = one stack). */ + private volatile int cargoHoldSalvageStackCount = -1; + private volatile boolean cargoHoldProcessing = false; + private int lastCargoHoldObjectId = -1; + private int cargoHoldWithdrawFailures = 0; + /** Consecutive withdraw clicks that did not change inventory (separate from open failures). */ + private int cargoHoldWithdrawNoGainStreak = 0; + private long lastCargoHoldInitAttemptMs; + private long lastCargoHoldInitHintLogMs; + private long lastCargoHoldWidgetResyncMs; @Inject - public SalvagingScript(Rs2TileObjectCache tileObjectCache, Rs2BoatCache boatCache) { + public SalvagingScript(Rs2TileObjectCache tileObjectCache, Rs2BoatCache boatCache, EventBus eventBus) { this.tileObjectCache = tileObjectCache; this.boatCache = boatCache; + this.eventBus = eventBus; + } + + public void register() { + eventBus.register(this); + } + + public void unregister() { + eventBus.unregister(this); + } + + @Subscribe + public void onGameTick(GameTick event) { + rebuildShipwreckMapsFromTopLevelScene(); + activeWreckSnapshot = List.copyOf(activeWreckByKey.values()); + inactiveWreckSnapshot = List.copyOf(inactiveWreckByKey.values()); + } + + /** + * Full top-level scene pass (sea layer), same objects the client draws for distant water tiles. + */ + private void rebuildShipwreckMapsFromTopLevelScene() { + activeWreckByKey.clear(); + inactiveWreckByKey.clear(); + Client client = Microbot.getClient(); + if (client == null) { + return; + } + WorldView wv = client.getTopLevelWorldView(); + if (wv == null) { + return; + } + Scene scene = wv.getScene(); + if (scene == null) { + return; + } + Tile[][][] tiles = scene.getTiles(); + if (tiles == null) { + return; + } + int plane = wv.getPlane(); + if (plane < 0) { + return; + } + if (plane >= tiles.length) { + return; + } + Tile[][] planeTiles = tiles[plane]; + if (planeTiles == null) { + return; + } + int maxX = Math.min(Constants.SCENE_SIZE, planeTiles.length); + for (int x = 0; x < maxX; x++) { + Tile[] column = planeTiles[x]; + if (column == null) { + continue; + } + int maxY = Math.min(Constants.SCENE_SIZE, column.length); + for (int y = 0; y < maxY; y++) { + Tile tile = column[y]; + if (tile == null) { + continue; + } + GameObject[] gameObjects = tile.getGameObjects(); + if (gameObjects != null) { + for (GameObject go : gameObjects) { + if (go == null) { + continue; + } + if (!go.getSceneMinLocation().equals(tile.getSceneLocation())) { + continue; + } + considerShipwreckTileObjectForRebuild(go); + } + } + DecorativeObject dec = tile.getDecorativeObject(); + if (dec != null) { + considerShipwreckTileObjectForRebuild(dec); + } + } + } + } + + private void considerShipwreckTileObjectForRebuild(TileObject obj) { + int id = obj.getId(); + if (SalvageObjectIds.ACTIVE_SHIPWRECK_IDS.contains(id)) { + activeWreckByKey.put(dedupeKey(obj), new Rs2TileObjectModel(obj)); + return; + } + if (SalvageObjectIds.INACTIVE_SHIPWRECK_IDS.contains(id)) { + inactiveWreckByKey.put(dedupeKey(obj), new Rs2TileObjectModel(obj)); + } + } + + private static String dedupeKey(TileObject o) { + WorldPoint p = o.getWorldLocation(); + return o.getId() + ":" + p.getX() + ":" + p.getY() + ":" + p.getPlane(); + } + + private static List mergeDistinctTileObjectLists(List a, List b) { + Map byKey = new LinkedHashMap<>(); + for (Rs2TileObjectModel m : a) { + byKey.put(tileObjectDedupeKey(m), m); + } + for (Rs2TileObjectModel m : b) { + String key = tileObjectDedupeKey(m); + if (!byKey.containsKey(key)) { + byKey.put(key, m); + } + } + return List.copyOf(byKey.values()); + } + + private static String tileObjectDedupeKey(Rs2TileObjectModel m) { + return dedupeKey(m); } public List getActiveWrecks() { - return tileObjectCache.query() - .where(wreck -> SalvageObjectIds.ACTIVE_SHIPWRECK_IDS.contains(wreck.getId())) - .toListOnClientThread(); + return activeWreckSnapshot; } public List getInactiveWrecks() { - return tileObjectCache.query() - .where(wreck -> SalvageObjectIds.INACTIVE_SHIPWRECK_IDS.contains(wreck.getId())) - .toListOnClientThread(); + return inactiveWreckSnapshot; } public void run(SailingConfig config) { try { var player = new Rs2PlayerModel(); + if (!config.useCargoHold()) { + resetCargoHoldState(); + } else { + if (cargoHoldCapacity == -1) { + initCargoHold(); + } + } + if (isPlayerAnimating(player)) { log.info("Currently salvaging, waiting..."); sleep(WAIT_TIME, WAIT_TIME_MAX); return; } - // Check inventory FIRST before deciding whether to salvage + if (config.useCargoHold()) { + if (handleCargoHoldMode(config, player)) { + return; + } + } + + if (tryRunIdleInventoryCleanup(config)) { + return; + } + if (isInventoryFull()) { - log.info("Inventory full, handling before salvaging"); + if (config.useCargoHold() && cargoHoldProcessing && hasSalvageItems()) { + log.info("Inventory full during cargo-hold processing; processing salvage at station before more withdraws"); + } else { + log.info("Inventory full, handling before salvaging"); + } handleFullInventory(config, player); return; } - // Inventory has space — go find a wreck and salvage var nearbyWreck = findNearestWreck(player.getWorldLocation()); if (nearbyWreck == null) { log.info("No shipwreck found nearby"); @@ -88,12 +307,907 @@ public void run(SailingConfig config) { } } + private void resetCargoHoldState() { + cargoHoldCapacity = -1; + cargoHoldCount = -1; + cargoHoldSalvageStackCount = -1; + cargoHoldProcessing = false; + lastCargoHoldObjectId = -1; + cargoHoldWithdrawFailures = 0; + cargoHoldWithdrawNoGainStreak = 0; + lastCargoHoldInitAttemptMs = 0; + lastCargoHoldInitHintLogMs = 0; + lastCargoHoldWidgetResyncMs = 0; + } + + /** + * @return true if this tick is fully handled and {@link #run(SailingConfig)} should return. + */ + private boolean handleCargoHoldMode(SailingConfig config, Rs2PlayerModel player) { + syncCargoHoldIfObjectVariantChanged(); + if (cargoHoldCapacity == -1) { + initCargoHold(); + if (cargoHoldCapacity == -1) { + logCargoHoldInitThrottled( + "Cargo hold not initialized yet; salvaging continues. Stand on your boat near the hold."); + return false; + } + } + + refreshCargoHoldCountsIfPanelOpen(); + + if (!cargoHoldProcessing) { + if (hasNearbySalvageableWreck(player.getWorldLocation()) || hasSalvageItems()) { + if (!willDepositSalvageToCargoHoldImminently()) { + maybeResyncCargoHoldCountsFromOpenUi(); + } + } + } + + if (cargoHoldProcessing || shouldProcessCargoHold()) { + if (!cargoHoldProcessing && shouldProcessCargoHold()) { + cargoHoldProcessing = true; + log.info("Cargo hold processing phase started (full or near capacity)"); + } + if (cargoHoldSalvageStackCount == 0) { + cargoHoldProcessing = false; + cargoHoldWithdrawFailures = 0; + cargoHoldWithdrawNoGainStreak = 0; + log.info("No salvage left in cargo hold, resuming normal salvaging"); + return false; + } + if (hasSalvageItems() && canDepositSalvageToCargoHold() + && !suppressSalvageDepositDuringCargoHoldProcessing()) { + depositToCargoHold(); + return true; + } + if (isInventoryFull()) { + handleFullInventory(config, player); + return true; + } + boolean fillingInventoryFromHold = !isInventoryFull() + && (cargoHoldSalvageStackCount > 0 + || (cargoHoldSalvageStackCount < 0 && cargoHoldCount > 0)); + if (!fillingInventoryFromHold && !hasNearbySalvageableWreck(player.getWorldLocation())) { + return false; + } + processCargoHoldWithdrawStep(); + return true; + } + + return false; + } + + private void syncCargoHoldIfObjectVariantChanged() { + if (cargoHoldCapacity < 0) { + return; + } + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + return; + } + int id = hold.getId(); + if (lastCargoHoldObjectId < 0) { + lastCargoHoldObjectId = id; + return; + } + if (id == lastCargoHoldObjectId) { + return; + } + lastCargoHoldObjectId = id; + Integer cap = CargoHoldObjectIds.ID_TO_CAPACITY.get(id); + if (cap != null) { + cargoHoldCapacity = cap; + } + clampCargoHoldCount(); + clampCargoHoldSalvageStackCount(); + } + + private void initCargoHold() { + if (cargoHoldCapacity != -1) { + return; + } + long now = System.currentTimeMillis(); + if (now - lastCargoHoldInitAttemptMs < 2500) { + return; + } + lastCargoHoldInitAttemptMs = now; + + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + logCargoHoldInitThrottled("Cargo hold: no cargo hold object in this scene (stand on your boat)."); + return; + } + Integer capObj = CargoHoldObjectIds.ID_TO_CAPACITY.get(hold.getId()); + if (capObj == null) { + logCargoHoldInitThrottled( + "Cargo hold: object id " + hold.getId() + " not mapped to capacity; add it to CargoHoldObjectIds if this is a new boat tier or variant."); + return; + } + int cap = capObj; + if (!openCargoHoldInterfaceForWithdraw()) { + logCargoHoldInitThrottled( + "Cargo hold: could not open interface for initialization; stand on your boat and use Open on the hold."); + return; + } + sleep(Rs2Random.between(280, 650)); + cargoHoldCapacity = cap; + if (!readOccupiedCountFromOpenHoldInterface()) { + cargoHoldCapacity = -1; + cargoHoldCount = -1; + cargoHoldSalvageStackCount = -1; + logCargoHoldInitThrottled( + "Cargo hold: could not read hold contents after opening; check client/game updates."); + closeCargoHoldInterface(); + return; + } + closeCargoHoldInterface(); + lastCargoHoldObjectId = hold.getId(); + lastCargoHoldInitHintLogMs = 0; + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + log.info( + "Cargo hold initialized: capacity={} slots, occupied={}, salvage stacks={}; deposits use in-UI Deposit inventory.", + cargoHoldCapacity, cargoHoldCount, cargoHoldSalvageStackCount); + } + + private void logCargoHoldInitThrottled(String message) { + long t = System.currentTimeMillis(); + if (t - lastCargoHoldInitHintLogMs < 15000) { + return; + } + lastCargoHoldInitHintLogMs = t; + log.info(message); + } + + /** + * Resolves the cargo hold on the client thread: tile cache merge, explicit walk of the local player's + * {@link WorldView} scene (same approach as Plugin Hub {@code BoatTracker} + {@code CargoHoldTier} ids), + * {@link Rs2GameObject} radius scan, then name match, then nearest to the player. + */ + private Rs2TileObjectModel findCargoHold() { + return Microbot.getClientThread().invoke(this::findCargoHoldOnClientThread); + } + + private Rs2TileObjectModel findCargoHoldOnClientThread() { + List fromWorldView = tileObjectCache.query() + .fromWorldView() + .where(this::isCargoHoldTileObject) + .toList(); + List fromDefaultScene = tileObjectCache.query() + .where(this::isCargoHoldTileObject) + .toList(); + List merged = mergeDistinctTileObjectLists(fromWorldView, fromDefaultScene); + merged = mergeDistinctTileObjectLists(merged, scanCargoHoldObjectsFromLocalPlayerWorldViewScene()); + merged = mergeDistinctTileObjectLists(merged, scanCargoHoldObjectsFromScene()); + if (merged.isEmpty()) { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor != null) { + try { + TileObject named = Rs2GameObject.getTileObject("Cargo hold", anchor, NEARBY_TILE_OBJECT_SCAN_RADIUS); + if (named != null) { + merged = List.of(new Rs2TileObjectModel(named)); + } + } catch (RuntimeException ex) { + log.debug("Cargo hold: Rs2GameObject.getTileObject name fallback failed (known issue on some sea scenes)", ex); + } + } + } + if (merged.isEmpty()) { + return null; + } + WorldPoint player = Rs2Player.getWorldLocation(); + if (player == null) { + return merged.get(0); + } + return merged.stream() + .min(Comparator.comparingInt(o -> player.distanceTo(o.getWorldLocation()))) + .orElse(null); + } + + private List scanCargoHoldObjectsFromScene() { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor == null) { + return List.of(); + } + try { + List raw = Rs2GameObject.getAll(o -> CargoHoldObjectIds.ALL_IDS.contains(o.getId()), anchor, NEARBY_TILE_OBJECT_SCAN_RADIUS); + List out = new ArrayList<>(); + for (Object o : raw) { + if (o instanceof TileObject) { + out.add(new Rs2TileObjectModel((TileObject) o)); + } + } + return out; + } catch (RuntimeException ex) { + log.debug("Cargo hold: Rs2GameObject.getAll scene scan failed", ex); + return List.of(); + } + } + + /** + * Full scene pass on the local player's {@link WorldView} (the boat interior when boarded), matching Plugin Hub + * {@code BoatTracker} / {@code CargoHoldTier.fromGameObjectId} behaviour. + */ + private List scanCargoHoldObjectsFromLocalPlayerWorldViewScene() { + Client client = Microbot.getClient(); + if (client == null) { + return List.of(); + } + Player lp = client.getLocalPlayer(); + if (lp == null) { + return List.of(); + } + WorldView wv = lp.getWorldView(); + if (wv == null) { + return List.of(); + } + return collectTileObjectsFromWorldViewScene(wv, this::isCargoHoldTileObject); + } + + /** + * Walks one {@link WorldView}'s scene (e.g. local player / boat interior) and collects {@link TileObject}s that + * match {@code predicate}. Same tile rules as {@link #rebuildShipwreckMapsFromTopLevelScene()} for game objects. + */ + private List collectTileObjectsFromWorldViewScene( + WorldView wv, + Predicate predicate) { + Scene scene = wv.getScene(); + if (scene == null) { + return List.of(); + } + Tile[][][] tiles = scene.getTiles(); + if (tiles == null) { + return List.of(); + } + int plane = wv.getPlane(); + if (plane < 0) { + return List.of(); + } + if (plane >= tiles.length) { + return List.of(); + } + Tile[][] planeTiles = tiles[plane]; + if (planeTiles == null) { + return List.of(); + } + List out = new ArrayList<>(); + int maxX = Math.min(Constants.SCENE_SIZE, planeTiles.length); + for (int x = 0; x < maxX; x++) { + Tile[] column = planeTiles[x]; + if (column == null) { + continue; + } + int maxY = Math.min(Constants.SCENE_SIZE, column.length); + for (int y = 0; y < maxY; y++) { + Tile tile = column[y]; + if (tile == null) { + continue; + } + GameObject[] gameObjects = tile.getGameObjects(); + if (gameObjects != null) { + for (GameObject go : gameObjects) { + if (go == null) { + continue; + } + if (!go.getSceneMinLocation().equals(tile.getSceneLocation())) { + continue; + } + maybeAddTileObjectIf(out, go, predicate); + } + } + DecorativeObject dec = tile.getDecorativeObject(); + if (dec != null) { + maybeAddTileObjectIf(out, dec, predicate); + } + } + } + return out; + } + + private static void maybeAddTileObjectIf( + List out, + TileObject obj, + Predicate predicate) { + Rs2TileObjectModel model = new Rs2TileObjectModel(obj); + if (!predicate.test(model)) { + return; + } + out.add(model); + } + + private boolean isCargoHoldTileObject(Rs2TileObjectModel obj) { + if (CargoHoldObjectIds.ALL_IDS.contains(obj.getId())) { + return true; + } + String name = obj.getName(); + if (name == null) { + return false; + } + return name.toLowerCase().contains("cargo hold"); + } + + private boolean shouldProcessCargoHold() { + if (cargoHoldCapacity < 0 || cargoHoldCount < 0) { + return false; + } + int freeSlots = cargoHoldCapacity - cargoHoldCount; + return freeSlots == 0 || freeSlots < Rs2Inventory.emptySlotCount(); + } + + /** + * True when the hold is initialized and has reported spare capacity and the player is carrying salvage. + * Intentionally does not use {@link #shouldProcessCargoHold()} — that check compares hold free slots to + * empty inventory slots, which stays true while the inventory is "full" (24+ items) but still has + * several empty spaces, and would block in-UI deposit even though the client may still allow it. + */ + private boolean canDepositSalvageToCargoHold() { + if (cargoHoldCapacity < 0) { + return false; + } + if (cargoHoldCount < 0) { + return false; + } + int free = cargoHoldCapacity - cargoHoldCount; + if (free <= 0) { + return false; + } + return Rs2Inventory.count("salvage") > 0; + } + + private void depositToCargoHold() { + if (!openCargoHoldInterfaceForWithdraw()) { + log.info("Cargo hold: could not open interface for deposit"); + return; + } + sleep(Rs2Random.between(200, 480)); + if (!readOccupiedCountAfterDepositWhileHoldOpen()) { + log.warn("Cargo hold: could not read hold grid from UI before deposit"); + closeCargoHoldInterface(); + return; + } + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + int salvageBefore = Rs2Inventory.count("salvage"); + if (salvageBefore <= 0) { + closeCargoHoldInterface(); + return; + } + if (!canDepositSalvageToCargoHold()) { + if (cargoHoldCapacity > 0) { + int free = cargoHoldCapacity - cargoHoldCount; + if (free <= 0) { + cargoHoldProcessing = true; + log.info( + "Cargo hold has no free slots after UI read ({} / {}); switching to processing phase", + cargoHoldCount, + cargoHoldCapacity); + } + } + closeCargoHoldInterface(); + return; + } + AtomicBoolean clicked = new AtomicBoolean(false); + Microbot.getClientThread().invoke(() -> clicked.set(clickDepositInventoryInOpenCargoHold())); + if (!clicked.get()) { + log.warn("Cargo hold: Deposit inventory control not found in interface"); + closeCargoHoldInterface(); + return; + } + sleep(Rs2Random.between(280, 620)); + sleepUntil(() -> Rs2Inventory.count("salvage") < salvageBefore, SALVAGE_TIMEOUT); + boolean readOk = readOccupiedCountAfterDepositWhileHoldOpen(); + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + if (!readOk) { + log.info("Cargo hold: could not refresh counts after deposit from UI"); + } + if (!shouldLeaveCargoHoldOpenAfterDeposit()) { + closeCargoHoldInterface(); + } + } + + /** + * When the cargo-hold pipeline will continue on the next script iteration (withdraw another stack, or deposit again + * with the UI already open), closing here forces an immediate re-open in {@link #processCargoHoldWithdrawStep()} or + * {@link #openCargoHoldInterfaceForWithdraw()}. Leave the panel open instead. + */ + private boolean shouldLeaveCargoHoldOpenAfterDeposit() { + if (cargoHoldSalvageStackCount <= 0) { + return false; + } + if (cargoHoldProcessing) { + return true; + } + return shouldProcessCargoHold(); + } + + /** + * Opens the cargo hold panel (Open on world object). Used for withdraw and for in-UI deposit. + */ + private boolean openCargoHoldInterfaceForWithdraw() { + if (Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)) { + return true; + } + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + return false; + } + hold.click("Open"); + return sleepUntil(() -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE), CARGO_HOLD_UI_TIMEOUT_MS); + } + + /** + * Reads occupied + salvage counts while the cargo-hold interface is already open. Call + * {@link #openCargoHoldInterfaceForWithdraw()} (and a short sleep) first when the panel was not open. + * + * @return false if the read failed + */ + private boolean readOccupiedCountFromOpenHoldInterface() { + int[] grid = Microbot.getClientThread().invoke(this::countOccupiedAndSalvageStacksInOpenHoldInterface); + if (grid == null) { + return false; + } + applyCargoHoldCountsFromItemGrid(grid); + return true; + } + + /** + * Reads occupied/salvage counts while the hold panel stays open: used before clicking Deposit inventory (sync state, + * avoid depositing into a full hold) and after a deposit (header line can lag). Settles then retries across ticks. + */ + private boolean readOccupiedCountAfterDepositWhileHoldOpen() { + sleep(Rs2Random.between(180, 420)); + for (int attempt = 0; attempt < CARGO_HOLD_POST_DEPOSIT_READ_ATTEMPTS; attempt++) { + if (readOccupiedCountFromOpenHoldInterface()) { + return true; + } + sleep(Rs2Random.between(CARGO_HOLD_POST_DEPOSIT_READ_GAP_MIN_MS, CARGO_HOLD_POST_DEPOSIT_READ_GAP_MAX_MS)); + } + return false; + } + + /** + * Re-reads occupied/salvage counts whenever the cargo-hold panel is already open (no throttle). Must run before + * deposit/withdraw decisions: throttled {@link #maybeResyncCargoHoldCountsFromOpenUi()}, skipped resync while + * {@link #cargoHoldProcessing}, and deposit-imminent skips left stale counts and repeated deposit attempts into a + * full hold. + */ + private void refreshCargoHoldCountsIfPanelOpen() { + boolean visible = Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)).orElse(false); + if (!visible) { + return; + } + if (readOccupiedCountFromOpenHoldInterface()) { + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + } + } + + /** + * Re-opens the hold on a throttle and re-counts widgets when the panel was closed. When the panel is open, + * {@link #refreshCargoHoldCountsIfPanelOpen()} already refreshed this tick. + */ + private void maybeResyncCargoHoldCountsFromOpenUi() { + boolean wasVisible = Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)).orElse(false); + if (wasVisible) { + return; + } + long now = System.currentTimeMillis(); + if (now - lastCargoHoldWidgetResyncMs < CARGO_HOLD_WIDGET_RESYNC_MIN_MS) { + return; + } + if (!openCargoHoldInterfaceForWithdraw()) { + return; + } + sleep(Rs2Random.between(180, 420)); + if (!readOccupiedCountFromOpenHoldInterface()) { + return; + } + lastCargoHoldWidgetResyncMs = now; + closeCargoHoldInterface(); + } + + private boolean clickDepositInventoryInOpenCargoHold() { + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + Widget universe = client.getWidget(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (universe == null || universe.isHidden()) { + return false; + } + Widget deposit = client.getWidget(InterfaceID.SailingBoatCargohold.DEPOSITALL_INVENTORY); + if (deposit != null && !deposit.isHidden()) { + Rs2Widget.clickWidget(deposit); + return true; + } + Widget target = findDepositInventoryWidget(universe); + if (target == null) { + return false; + } + Rs2Widget.clickWidget(target); + return true; + } + + private static Widget findDepositInventoryWidget(Widget w) { + if (w == null) { + return null; + } + String[] actions = w.getActions(); + if (actions != null) { + for (String a : actions) { + if (a == null) { + continue; + } + String lower = a.toLowerCase(); + if (lower.contains("deposit") && lower.contains("inventory")) { + return w; + } + } + } + String text = w.getText(); + if (text != null) { + String lower = text.toLowerCase().replace("
", " "); + if (lower.contains("deposit") && lower.contains("inventory")) { + return w; + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return null; + } + for (Widget c : children) { + Widget found = findDepositInventoryWidget(c); + if (found != null) { + return found; + } + } + return null; + } + + /** + * Applies {@code grid[0]} = occupied slots, {@code grid[1]} = salvage stack count from the ITEMS grid. + * {@link #cargoHoldCapacity} is unchanged here (set from {@link CargoHoldObjectIds} at init). + */ + private void applyCargoHoldCountsFromItemGrid(int[] grid) { + if (grid == null) { + return; + } + if (grid.length < 2) { + return; + } + int occ = Math.max(0, grid[0]); + int sal = Math.max(0, grid[1]); + if (cargoHoldCapacity > 0) { + cargoHoldCount = Math.min(occ, cargoHoldCapacity); + cargoHoldSalvageStackCount = Math.min(sal, cargoHoldCount); + return; + } + cargoHoldCount = occ; + cargoHoldSalvageStackCount = Math.min(sal, occ); + } + + private void closeCargoHoldInterface() { + if (!Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)) { + return; + } + sleep(Rs2Random.between(280, 620)); + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + sleep(Rs2Random.between(280, 520)); + } + + /** + * {@code [0]} = occupied slots, {@code [1]} = salvage-named stacks in the ITEMS grid. + * Occupied comes from {@link CargoHoldInterfaceWidgets} (occupied-only text child {@code 943, 4}) when parseable; else the ITEMS grid walk. + * Requires the cargo-hold panel to already be open. Client thread only. + */ + private int[] countOccupiedAndSalvageStacksInOpenHoldInterface() { + try { + Client client = Microbot.getClient(); + if (client == null) { + return null; + } + Widget universe = client.getWidget(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (universe == null || universe.isHidden()) { + return null; + } + Widget items = client.getWidget(InterfaceID.SailingBoatCargohold.ITEMS); + if (items == null || items.isHidden()) { + return null; + } + int salvageStacks = countSalvageItemSlotsInHoldRecursive(client, items); + Integer occupiedFromLine = parseOccupiedSlotsFromCargoHoldTextLine(client); + int occupied; + if (occupiedFromLine != null) { + occupied = occupiedFromLine; + } else { + occupied = countNonEmptyItemSlotsRecursive(items); + } + return new int[] { occupied, salvageStacks }; + } catch (RuntimeException ex) { + log.debug("Cargo hold: interface read failed", ex); + return null; + } + } + + /** + * First number in the occupied-slot widget text (typically just occupied {@code X}; same regex if a {@code X / N} string appears). + */ + private static Integer parseOccupiedSlotsFromCargoHoldTextLine(Client client) { + Widget w = client.getWidget( + CargoHoldInterfaceWidgets.CARGO_HOLD_OCCUPIED_TEXT_GROUP, + CargoHoldInterfaceWidgets.CARGO_HOLD_OCCUPIED_TEXT_CHILD); + if (w == null) { + return null; + } + if (w.isHidden()) { + return null; + } + String t = w.getText(); + if (t == null) { + return null; + } + if (t.isEmpty()) { + return null; + } + String plain = Text.removeTags(t).replace("
", " ").trim(); + Matcher m = CARGO_HOLD_FIRST_NUMBER.matcher(plain); + if (!m.find()) { + return null; + } + return Integer.parseInt(m.group(1)); + } + + private static int countSalvageItemSlotsInHoldRecursive(Client client, Widget w) { + int count = 0; + if (w.getItemId() > 0) { + var def = client.getItemDefinition(w.getItemId()); + if (def != null) { + String name = def.getName(); + if (name != null) { + if (name.toLowerCase().contains("salvage")) { + count++; + } + } + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return count; + } + for (Widget c : children) { + if (c == null) { + continue; + } + count += countSalvageItemSlotsInHoldRecursive(client, c); + } + return count; + } + + private static int countNonEmptyItemSlotsRecursive(Widget w) { + int count = 0; + if (w.getItemId() > 0) { + count++; + } + Widget[] children = w.getChildren(); + if (children == null) { + return count; + } + for (Widget c : children) { + if (c == null) { + continue; + } + count += countNonEmptyItemSlotsRecursive(c); + } + return count; + } + + private static int countSalvageItemStacksInInventory() { + int n = 0; + for (Rs2ItemModel item : Rs2Inventory.all()) { + String name = item.getName(); + if (name == null) { + continue; + } + if (name.toLowerCase().contains("salvage")) { + n++; + } + } + return n; + } + + private void processCargoHoldWithdrawStep() { + boolean holdWasAlreadyOpen = Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (!openCargoHoldInterfaceForWithdraw()) { + cargoHoldWithdrawFailures++; + if (cargoHoldWithdrawFailures >= CARGO_HOLD_WITHDRAW_FAIL_THRESHOLD) { + log.warn("Cargo hold: interface failed to open repeatedly; exiting processing mode"); + cargoHoldProcessing = false; + cargoHoldWithdrawFailures = 0; + } + return; + } + cargoHoldWithdrawFailures = 0; + + if (!holdWasAlreadyOpen) { + sleep(Rs2Random.between(280, 650)); + } + sleep(Rs2Random.between(120, 320)); + + int trackedSalvageBeforeRead = cargoHoldSalvageStackCount; + readOccupiedCountFromOpenHoldInterface(); + if (cargoHoldSalvageStackCount == 0) { + if (trackedSalvageBeforeRead > 0) { + sleep(Rs2Random.between(450, 900)); + readOccupiedCountFromOpenHoldInterface(); + } + } + if (cargoHoldSalvageStackCount == 0) { + closeCargoHoldInterface(); + return; + } + + int salvageBefore = Rs2Inventory.count("salvage"); + AtomicBoolean invoked = new AtomicBoolean(false); + Microbot.getClientThread().invoke(() -> invoked.set(invokeWithdrawOneSalvageStackFromCargoHoldUi())); + if (!invoked.get()) { + log.info("Cargo hold: no salvage stack in hold UI; re-reading occupied count from open interface"); + readOccupiedCountFromOpenHoldInterface(); + closeCargoHoldInterface(); + if (cargoHoldSalvageStackCount == 0) { + cargoHoldProcessing = false; + } + return; + } + + // Only after the salvage slot click — long waits belong here, not before the click. + sleep(Rs2Random.between(CARGO_HOLD_POST_WITHDRAW_CLICK_MIN_MS, CARGO_HOLD_POST_WITHDRAW_CLICK_MAX_MS)); + boolean gainedInventory = sleepUntil( + () -> Rs2Inventory.count("salvage") != salvageBefore, CARGO_HOLD_WITHDRAW_INVENTORY_TIMEOUT_MS); + if (!gainedInventory) { + sleep(Rs2Random.between(650, 1400)); + gainedInventory = sleepUntil(() -> Rs2Inventory.count("salvage") != salvageBefore, 7000); + } + if (!gainedInventory) { + cargoHoldWithdrawNoGainStreak++; + log.info("Cargo hold: withdraw not reflected in inventory yet; leaving hold open for retry (avoid closing before click applies)"); + if (cargoHoldWithdrawNoGainStreak >= CARGO_HOLD_WITHDRAW_NO_GAIN_THRESHOLD) { + log.warn("Cargo hold: withdraw inventory never updated; closing interface and exiting processing mode"); + closeCargoHoldInterface(); + cargoHoldProcessing = false; + cargoHoldWithdrawNoGainStreak = 0; + } + return; + } + cargoHoldWithdrawNoGainStreak = 0; + + int gained = Rs2Inventory.count("salvage") - salvageBefore; + int countBeforeUiRead = cargoHoldCount; + int salvageCountBeforeUiRead = cargoHoldSalvageStackCount; + readOccupiedCountFromOpenHoldInterface(); + if (gained > 0) { + if (cargoHoldCount == countBeforeUiRead) { + cargoHoldCount = Math.max(0, cargoHoldCount - 1); + clampCargoHoldCount(); + } + if (cargoHoldSalvageStackCount == salvageCountBeforeUiRead && cargoHoldSalvageStackCount > 0) { + cargoHoldSalvageStackCount = Math.max(0, cargoHoldSalvageStackCount - 1); + } + clampCargoHoldSalvageStackCount(); + } + + Rs2Antiban.actionCooldown(); + if (isInventoryFull()) { + sleep(Rs2Random.between(CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MIN_MS, CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MAX_MS)); + if (Rs2Random.dicePercentage(18)) { + Rs2Antiban.takeMicroBreakByChance(); + } + closeCargoHoldInterface(); + return; + } + if (cargoHoldSalvageStackCount == 0) { + closeCargoHoldInterface(); + return; + } + sleep(Rs2Random.between(180, 480)); + } + + /** + * Left-clicks the salvage stack widget in the open cargo-hold item grid ({@code Rs2Widget.clickWidget}). + * Real widget click (same pattern as other hub plugins), not a synthesized menu entry. + */ + private boolean invokeWithdrawOneSalvageStackFromCargoHoldUi() { + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + Widget salvageSlot = findFirstSalvageStackWidget(client); + if (salvageSlot == null) { + return false; + } + Rs2Widget.clickWidget(salvageSlot); + return true; + } + + private static Widget findFirstSalvageStackWidget(Client client) { + Widget items = client.getWidget(InterfaceID.SailingBoatCargohold.ITEMS); + if (items == null) { + return null; + } + return findSalvageInTree(client, items); + } + + private static Widget findSalvageInTree(Client client, Widget w) { + if (w == null) { + return null; + } + if (w.getItemId() > 0) { + var def = client.getItemDefinition(w.getItemId()); + if (def != null && def.getName().toLowerCase().contains("salvage")) { + return w; + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return null; + } + for (Widget c : children) { + Widget found = findSalvageInTree(client, c); + if (found != null) { + return found; + } + } + return null; + } + + private void clampCargoHoldCount() { + if (cargoHoldCapacity < 0) { + return; + } + if (cargoHoldCount < 0) { + cargoHoldCount = 0; + } + if (cargoHoldCount > cargoHoldCapacity) { + cargoHoldCount = cargoHoldCapacity; + } + } + + private void clampCargoHoldSalvageStackCount() { + if (cargoHoldSalvageStackCount < 0) { + return; + } + cargoHoldSalvageStackCount = Math.max(0, cargoHoldSalvageStackCount); + if (cargoHoldCount >= 0) { + cargoHoldSalvageStackCount = Math.min(cargoHoldSalvageStackCount, cargoHoldCount); + } + } + private boolean isPlayerAnimating(Rs2PlayerModel player) { return player.getAnimation() != -1; } + /** Stable threshold so cargo-hold processing does not alternate ticks between "full" and not full. */ private boolean isInventoryFull() { - return Rs2Inventory.count() >= Rs2Random.between(MIN_INVENTORY_FULL, MAX_INVENTORY_FULL); + return Rs2Inventory.count() >= MIN_INVENTORY_FULL; + } + + /** + * While {@link #cargoHoldProcessing} and the hold still has salvage stacks ({@link #cargoHoldSalvageStackCount} + * > 0), do not {@link #depositToCargoHold()}. Non-salvage items may still occupy slots; deposits resume when + * salvage stacks in the hold reach 0. + */ + private boolean suppressSalvageDepositDuringCargoHoldProcessing() { + return cargoHoldProcessing && cargoHoldSalvageStackCount > 0; + } + + /** + * When true, this tick will open the hold for {@link #depositToCargoHold()} (from cargo-hold mode or + * {@link #handleFullInventory}). Skipping {@link #maybeResyncCargoHoldCountsFromOpenUi()} avoids an extra + * open→read→close before that deposit, which already refreshes counts after deposit. + */ + private boolean willDepositSalvageToCargoHoldImminently() { + return hasSalvageItems() + && canDepositSalvageToCargoHold() + && !suppressSalvageDepositDuringCargoHoldProcessing(); } private boolean hasSalvageItems() { @@ -119,23 +1233,128 @@ private boolean isWithinSalvageArea(WorldPoint playerLocation, Rs2TileObjectMode return playerLocation.distanceTo(wreck.getWorldLocation()) <= SIZE_SALVAGEABLE_AREA; } + /** + * True if any active shipwreck is within hook range. Used to avoid opening the cargo hold for withdraw processing + * while idle with no wreck nearby (which would spam open/close every script tick). + */ + private boolean hasNearbySalvageableWreck(WorldPoint playerLocation) { + for (Rs2TileObjectModel wreck : getActiveWrecks()) { + if (isWithinSalvageArea(playerLocation, wreck)) { + return true; + } + } + return false; + } + + /** + * After cargo-hold mass processing, inventory can sit below the "full" threshold while still holding + * drop/alch/casket loot. Runs one {@link #clearInventoryViaAlchDropAndCaskets} pass when there is no salvage to + * protect and configured cleanup would change the inventory. + * + * @return true if a cleanup pass was executed (caller should return for this tick). + */ + private boolean tryRunIdleInventoryCleanup(SailingConfig config) { + if (hasSalvageItems()) { + return false; + } + if (!inventoryCleanupConfigured(config)) { + return false; + } + if (!inventoryHasCleanupWork(config)) { + return false; + } + log.info("Inventory cleanup (drop/alch/caskets) before salvaging"); + clearInventoryViaAlchDropAndCaskets(config); + return true; + } + + private boolean inventoryCleanupConfigured(SailingConfig config) { + if (config.openCaskets()) { + return true; + } + String drop = config.dropItems(); + if (drop != null) { + if (!drop.isBlank()) { + return true; + } + } + if (!config.enableAlching()) { + return false; + } + String alch = config.alchItems(); + return alch != null && !alch.isBlank(); + } + + private boolean inventoryHasCleanupWork(SailingConfig config) { + if (config.openCaskets()) { + if (Rs2Inventory.hasItem("casket")) { + return true; + } + } + String dropItems = config.dropItems(); + if (dropItems != null) { + if (!dropItems.isBlank()) { + for (String raw : dropItems.split(",")) { + String name = raw.trim(); + if (name.isEmpty()) { + continue; + } + if (Rs2Inventory.hasItem(name)) { + return true; + } + } + } + } + if (config.enableAlching()) { + String alchItems = config.alchItems(); + if (alchItems != null) { + if (!alchItems.isBlank()) { + List fragments = Arrays.stream(alchItems.split(",")) + .map(String::trim) + .map(String::toLowerCase) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + for (Rs2ItemModel item : Rs2Inventory.all()) { + String n = item.getName(); + if (n == null) { + continue; + } + String lower = n.toLowerCase(); + for (String fragment : fragments) { + if (lower.contains(fragment)) { + return true; + } + } + } + } + } + } + return false; + } + private void handleFullInventory(SailingConfig config, Rs2PlayerModel player) { if (hasSalvageItems() && !isPlayerAnimating(player)) { - depositSalvageOrDrop(config); - } else { - // 1. Drop junk first to make room for casket loot - dropJunk(config); - // 2. Open caskets — now there's space for the loot to land - if (config.openCaskets()) { - openCaskets(); - } - // 3. Alch anything on the alch list (including new loot from caskets) - if (config.enableAlching()) { - alchItems(config); + if (config.useCargoHold()) { + if (canDepositSalvageToCargoHold() && !suppressSalvageDepositDuringCargoHoldProcessing()) { + depositToCargoHold(); + return; + } } - // 4. Drop anything leftover from casket loot that's also on the drop list - dropJunk(config); + depositSalvageOrDrop(config); + return; + } + clearInventoryViaAlchDropAndCaskets(config); + } + + private void clearInventoryViaAlchDropAndCaskets(SailingConfig config) { + dropJunk(config); + if (config.openCaskets()) { + openCaskets(); + } + if (config.enableAlching()) { + alchItems(config); } + dropJunk(config); } private void depositSalvageOrDrop(SailingConfig config) { @@ -149,14 +1368,85 @@ private void depositSalvageOrDrop(SailingConfig config) { } } + /** + * Resolves a salvaging station like {@link #findCargoHold()}: tile cache, explicit local {@link WorldView} scene walk + * (on-board station), then {@link Rs2GameObject} radius scan (e.g. port), nearest to the player. + */ private Rs2TileObjectModel findSalvagingStation() { - var playerWorldView = new Rs2PlayerModel().getWorldView().getId(); + return Microbot.getClientThread().invoke(this::findSalvagingStationOnClientThread); + } - return tileObjectCache.query() + private Rs2TileObjectModel findSalvagingStationOnClientThread() { + List fromWorldView = tileObjectCache.query() .fromWorldView() - .where(obj -> obj.getName() != null && obj.getName().equalsIgnoreCase("salvaging station")) - .where(obj -> obj.getWorldView().getId() == playerWorldView) - .nearestOnClientThread(); + .where(this::isSalvagingStationTileObject) + .toList(); + List fromDefaultScene = tileObjectCache.query() + .where(this::isSalvagingStationTileObject) + .toList(); + List merged = mergeDistinctTileObjectLists(fromWorldView, fromDefaultScene); + Client client = Microbot.getClient(); + if (client != null) { + Player lp = client.getLocalPlayer(); + if (lp != null) { + WorldView wv = lp.getWorldView(); + if (wv != null) { + merged = mergeDistinctTileObjectLists( + merged, + collectTileObjectsFromWorldViewScene(wv, this::isSalvagingStationTileObject)); + } + } + } + merged = mergeDistinctTileObjectLists(merged, scanSalvagingStationsFromRs2GameObject()); + if (merged.isEmpty()) { + return null; + } + WorldPoint player = Rs2Player.getWorldLocation(); + if (player == null) { + return merged.get(0); + } + return merged.stream() + .min(Comparator.comparingInt(o -> player.distanceTo(o.getWorldLocation()))) + .orElse(null); + } + + private List scanSalvagingStationsFromRs2GameObject() { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor == null) { + return List.of(); + } + try { + List raw = Rs2GameObject.getAll( + o -> SalvagingStationObjectIds.ALL_IDS.contains(o.getId()), + anchor, + NEARBY_TILE_OBJECT_SCAN_RADIUS); + List out = new ArrayList<>(); + for (Object o : raw) { + if (o instanceof TileObject) { + out.add(new Rs2TileObjectModel((TileObject) o)); + } + } + return out; + } catch (RuntimeException ex) { + log.debug("Salvaging station: Rs2GameObject.getAll scan failed", ex); + return List.of(); + } + } + + /** + * Boat and port salvaging stations are identified by object ID ({@code ObjectID1.SAILING_SALVAGING_STATION_*}) + * because composition objects often do not expose the exact menu name "Salvaging station" via + * {@link Rs2TileObjectModel#getName()}. + */ + private boolean isSalvagingStationTileObject(Rs2TileObjectModel obj) { + if (SalvagingStationObjectIds.ALL_IDS.contains(obj.getId())) { + return true; + } + String name = obj.getName(); + if (name == null) { + return false; + } + return name.equalsIgnoreCase("salvaging station"); } private void depositAtStation(Rs2TileObjectModel station) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java new file mode 100644 index 0000000000..1e46dccc86 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +import com.google.common.collect.ImmutableSet; +import net.runelite.api.gameval.ObjectID1; + +import java.util.Set; + +/** + * Tile object IDs for salvaging stations the script can deposit shipwreck salvage into. + * Boat stations are matched by ID because {@code Rs2TileObjectModel#getName()} is often null or not the + * literal phrase "salvaging station" for sailing compositions. + */ +public final class SalvagingStationObjectIds { + + private SalvagingStationObjectIds() { + } + + public static final Set ALL_IDS = ImmutableSet.of( + ObjectID1.SAILING_SALVAGING_STATION_2X5A, + ObjectID1.SAILING_SALVAGING_STATION_2X5B, + ObjectID1.SAILING_SALVAGING_STATION_3X8, + ObjectID1.SAILING_PORT_SALVAGING_STATION + ); +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java b/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java index 8a3263349e..dc8ad429fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java @@ -9,8 +9,6 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -151,16 +149,15 @@ public int getMaxTrapsForHunterLevel(SalamanderConfig config) { } private boolean handleExistingTraps(SalamanderPlugin plugin, SalamanderConfig config) { - // Filter for FULL traps and sort by time (traps about to collapse first) and then pick the first one var trapToHandle = plugin.getTraps().entrySet().stream() .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) .sorted((a, b) -> Double.compare(b.getValue().getTrapTimeRelative(), a.getValue().getTrapTimeRelative())).collect(Collectors.toList()).stream().findFirst().orElse(null); if (trapToHandle == null) return false; WorldPoint location = trapToHandle.getKey(); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving()) { - var gameObject = Rs2GameObject.getGameObject(location); + var gameObject = Microbot.getRs2TileObjectCache().query().within(location, 0).first(); if (gameObject != null) { - Rs2GameObject.interact(gameObject, "Reset"); + gameObject.click("Reset"); SalamandersCaught++; sleep(config.minSleepAfterCatch(), config.maxSleepAfterCatch()); return true; @@ -170,14 +167,15 @@ private boolean handleExistingTraps(SalamanderPlugin plugin, SalamanderConfig co } private void setNewTrap(SalamanderHunting salamanderType, SalamanderConfig config) { - if (Rs2GameObject.exists(salamanderType.getTreeId())) { - Rs2GameObject.interact(salamanderType.getTreeId(), "Set-trap"); + if (Microbot.getRs2TileObjectCache().query().withId(salamanderType.getTreeId()).count() > 0) { + Microbot.getRs2TileObjectCache().query().interact(salamanderType.getTreeId(), "Set-trap"); sleep(config.minSleepAfterLay(), config.maxSleepAfterLay()); } } public boolean IsRopeOnTheGround() { - return Rs2GroundItem.exists(ROPE, 7) || Rs2GroundItem.exists(303, 7); + return Microbot.getRs2TileItemCache().query().withId(ROPE).within(7).count() > 0 || + Microbot.getRs2TileItemCache().query().withId(303).within(7).count() > 0; } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java index 40f67c28e0..f537c73603 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class SandCrabPlugin extends Plugin { - public final static String version = "1.5.2"; + public final static String version = "1.5.3"; @Inject private SandCrabConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java index 4d36a7f122..3e786bbf66 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.sandcrabs; import net.runelite.api.GameState; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,8 +12,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.security.Login; @@ -207,18 +205,17 @@ public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Rs2Npc.getNpcs("Sandy rocks", true).collect(Collectors.toList()); + List npcs = Microbot.getRs2NpcCache().query().withName("Sandy rocks").toListOnClientThread(); if (npcs.isEmpty()) { return false; } - for (NPC sandyRock : npcs) { - //ignore sandcrabs far away from the player - if (!sandyRock.getWorldArea().isInMeleeDistance(Rs2Player.getWorldLocation())) + for (Rs2NpcModel sandyRock : npcs) { + if (!sandyRock.getNpc().getWorldArea().isInMeleeDistance(Rs2Player.getWorldLocation())) continue; - return false; //found a sandy rock crab near the player + return false; } - return true; //did not find any sandy rocks near the player + return true; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java index 77e000cd83..784054c13b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class GabulhasSandMinerPlugin extends Plugin { - public static final String version = "1.2.1"; + public static final String version = "1.2.2"; @Inject GabulhasSandMinerScript gabulhasSandMinerScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java index 1fd3aa9b37..e3236b1ca0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.sandminer; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; @@ -12,7 +11,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; @@ -111,17 +109,21 @@ private void miningLoop(GabulhasSandMinerConfig config) { if (firstRock) { WorldPoint innerMiningPoint = (Rs2Random.dicePercentage(50)) ? new WorldPoint(3164, 2905, 0) : new WorldPoint(3166, 2905, 0); - GameObject innerSandstoneRock = Rs2GameObject.getGameObject("Sandstone rocks", true, innerMiningPoint); - Rs2GameObject.interact(innerSandstoneRock, "Mine"); + var innerSandstoneRock = Microbot.getRs2TileObjectCache().query() + .withName("Sandstone rocks") + .nearestOnClientThread(innerMiningPoint, 0); + if (innerSandstoneRock != null) innerSandstoneRock.click("Mine"); Rs2Player.waitForXpDrop(Skill.MINING, 15000); Rs2Antiban.actionCooldown(); firstRock = false; continue; } } - GameObject sandstoneRock = Rs2GameObject.getGameObject("Sandstone rocks", true, miningPoint, 5); + var sandstoneRock = Microbot.getRs2TileObjectCache().query() + .withName("Sandstone rocks") + .nearestOnClientThread(miningPoint, 5); if (sandstoneRock != null) { - Rs2GameObject.interact(sandstoneRock, "Mine"); + sandstoneRock.click("Mine"); if (config.turboMode()) { Rs2Player.waitForXpDrop(Skill.MINING, 15000); } else { @@ -156,8 +158,10 @@ private void dropEmptyWaterskins() { private void deposit(GabulhasSandMinerConfig config) { if (!config.turboMode()) Rs2Walker.walkTo(grinder); - GameObject sandstoneRock = Rs2GameObject.findObject(26199, grinder); - Rs2GameObject.interact(sandstoneRock, "Deposit"); + var grinderObj = Microbot.getRs2TileObjectCache().query() + .withId(26199) + .nearest(grinder, 5); + if (grinderObj != null) grinderObj.click("Deposit"); while (Rs2Inventory.contains("Sandstone (1kg)", "Sandstone (2kg)", "Sandstone (5kg)", "Sandstone (10kg)") && super.isRunning()) { if (!config.turboMode()) { sleep(100, 3000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java index 256fe3e899..b23f8b71e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class ScurriusPlugin extends Plugin { - public static final String version = "1.0.1"; + public static final String version = "1.0.2"; @Inject private ScurriusConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java index 0a45b9bb7d..28619c4ed7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java @@ -11,12 +11,10 @@ import net.runelite.client.plugins.microbot.scurrius.enums.State; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.coords.Rs2LocalPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -26,7 +24,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -70,13 +67,13 @@ public boolean run(ScurriusConfig config) { previousState = state; } - scurrius = Rs2Npc.getNpc("Scurrius", true); + scurrius = Microbot.getRs2NpcCache().query().withName("Scurrius").nearestOnClientThread(); boolean hasFood = !Rs2Inventory.getInventoryFood().isEmpty(); boolean hasPrayerPotions = Rs2Inventory.hasItem("prayer potion") || Rs2Inventory.hasItem("super restore"); boolean isScurriusPresent = scurrius != null; boolean isInFightRoom = isInFightRoom(); - boolean hasLineOfSightWithScurrius = Rs2Npc.hasLineOfSight(scurrius); + boolean hasLineOfSightWithScurrius = scurrius != null && scurrius.hasLineOfSight(); if (previousInFightRoom == null || isInFightRoom != previousInFightRoom) { Microbot.log(isInFightRoom ? "Player has entered the boss room." : "Player has exited the boss room."); @@ -195,15 +192,17 @@ public boolean run(ScurriusConfig config) { } } - Optional giantRat = Rs2Npc.getNpcs("giant rat").filter(npc -> !npc.isDead()).findFirst(); - if (giantRat.isPresent()) { - Rs2NpcModel giantRatModel = giantRat.get(); - boolean didWeAttackAGiantRat = scurrius != null && config.prioritizeRats() && Rs2Npc.attack(giantRatModel); + Rs2NpcModel giantRatModel = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("giant rat") + && !n.getNpc().isDead()) + .nearest(); + if (giantRatModel != null) { + boolean didWeAttackAGiantRat = scurrius != null && config.prioritizeRats() && giantRatModel.click("Attack"); if (didWeAttackAGiantRat) return; } if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2Npc.attack(scurrius); + scurrius.click("Attack"); } break; @@ -225,7 +224,7 @@ public boolean run(ScurriusConfig config) { Rs2Walker.walkTo(bossLocation); String interactionType = config.bossRoomEntryType().getInteractionText(); - Rs2GameObject.interact(ObjectID.RAT_BOSS_ENTRANCE, interactionType); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RAT_BOSS_ENTRANCE).interact(interactionType); sleepUntil(this::isInFightRoom); break; @@ -329,7 +328,7 @@ private List parseLootItems(String lootFilter) { private void handlePrayerLogic() { if (scurrius == null) return; - int npcAnimation = scurrius.getAnimation(); + int npcAnimation = scurrius.getNpc().getAnimation(); Rs2PrayerEnum newDefensivePrayer = null; switch (npcAnimation) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java index 8c08163e12..f7aa1091b0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java @@ -25,7 +25,7 @@ @Slf4j public class ShadesKillerPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; @Inject private ShadesKillerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java index fa4558d927..d8a053817f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java @@ -9,7 +9,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -199,15 +199,18 @@ public boolean run(ShadesKillerConfig config) { case FIGHT_SHADES: boolean isLooting = Rs2GroundItem.lootAtGePrice(config.priceOfItemsToLoot()); if (isLooting) return; - var npc = Rs2Npc.getNpcsForPlayer(config.SHADES().names.get(0)).stream().findFirst().orElse(null); - //if npc is attacking us, then attack back + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .withName(config.SHADES().names.get(0)) + .where(Rs2NpcModel::isInteractingWithPlayer) + .nearestOnClientThread(); if (npc != null && !Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2Npc.attack(npc); + npc.click("Attack"); return; } - //if no npc is attacking us, attack a new npc if (!Rs2Combat.inCombat() && !isLooting) { - Rs2Npc.attack(config.SHADES().names); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query() + .withNames(config.SHADES().names.toArray(new String[0])) + .interact("Attack")); } Rs2Combat.setSpecState(true, config.specialAttack() * 10); if (Rs2Inventory.isFull() && config.useCoffin()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java index 8efd7ba1a7..3853812c87 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java @@ -73,7 +73,7 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.4"; + static final String version = "1.4.7"; @Getter private final List starList = new ArrayList<>(); @@ -398,6 +398,8 @@ public Star getClosestHighestTierStar() { // Get the highest tier available int highestTier = starList.stream() + .filter(Objects::nonNull) + .filter(s -> s.getShootingStarLocation() != null) .filter(s -> !s.isHidden() && s.hasRequirements()) .mapToInt(Star::getTier) .max() @@ -413,6 +415,8 @@ public Star getClosestHighestTierStar() int maxTier = Math.min(9, highestTier + 1); // The highest tier to consider (up to 9) List accessibleStars = starList.stream() + .filter(Objects::nonNull) + .filter(s -> s.getShootingStarLocation() != null) .filter(s -> !s.isHidden() && s.hasRequirements()) .filter(s -> s.getTier() >= minTier && s.getTier() <= maxTier) .sorted(Comparator.comparingInt(Star::getTier).reversed()) @@ -434,7 +438,20 @@ public Star getClosestHighestTierStar() ShortestPathPlugin.getPathfinderConfig().refresh(); } - Pathfinder pathfinder = new Pathfinder(ShortestPathPlugin.getPathfinderConfig(), Microbot.getClient().getLocalPlayer().getWorldLocation(), accessibleStarPoints); + WorldPoint playerLocation = Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (Microbot.getClient().getLocalPlayer() == null) + { + return null; + } + return Microbot.getClient().getLocalPlayer().getWorldLocation(); + }).orElse(null); + + if (playerLocation == null) + { + return null; + } + + Pathfinder pathfinder = new Pathfinder(ShortestPathPlugin.getPathfinderConfig(), playerLocation, accessibleStarPoints); pathfinder.run(); List path = pathfinder.getPath(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java index 4b826739a2..9694832728 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java @@ -7,10 +7,10 @@ import java.util.stream.Collectors; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ObjectID; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; @@ -28,7 +28,6 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Gembag; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -215,11 +214,11 @@ public boolean run() Rs2Combat.setSpecState(true, 1000); } - TileObject starObject = Rs2GameObject.getGameObject(currentStar.getObjectId()); + var starObject = Microbot.getRs2TileObjectCache().query().withId(currentStar.getObjectId()).nearest(); if (starObject != null) { - Rs2GameObject.interact(starObject, "mine"); + starObject.click("mine"); sleepUntil(Rs2Player::isAnimating); Rs2Antiban.actionCooldown(); Rs2Antiban.moveMouseOffScreen(); @@ -438,9 +437,21 @@ private ShootingStarState updateStarState() if (state == ShootingStarState.MINING) { - GameObject starObject = Rs2GameObject.getGameObject("crashed star", initialPlayerLocation, 10); - - if (currentStar == null || starObject == null) + var starModel = Microbot.getRs2TileObjectCache().query() + .withIds( + ObjectID.STAR_SIZE_ONE_STAR, + ObjectID.STAR_SIZE_TWO_STAR, + ObjectID.STAR_SIZE_THREE_STAR, + ObjectID.STAR_SIZE_FOUR_STAR, + ObjectID.STAR_SIZE_FIVE_STAR, + ObjectID.STAR_SIZE_SIX_STAR, + ObjectID.STAR_SIZE_SEVEN_STAR, + ObjectID.STAR_SIZE_EIGHT_STAR, + ObjectID.STAR_SIZE_NINE_STAR + ) + .nearest(initialPlayerLocation, 10); + + if (currentStar == null || starModel == null) { if (plugin.getSelectedStar().getTier() == 1) @@ -458,7 +469,7 @@ private ShootingStarState updateStarState() return ShootingStarState.WAITING_FOR_STAR; } - int _newTier = currentStar.getTierBasedOnObjectId(starObject.getId()); + int _newTier = currentStar.getTierBasedOnObjectId(starModel.getId()); currentStar.setTier(_newTier); plugin.updatePanelList(false); currentStar = selectedStar; @@ -491,22 +502,32 @@ private boolean hasStateChanged() // If the state is mining state, scan the crashed star game object & check if the game object id has updated. if (state == ShootingStarState.MINING) { - GameObject starObject = Rs2GameObject.getGameObject("crashed star", initialPlayerLocation, 10); - return hasStarGameObjectChanged(starObject); + var starModel = Microbot.getRs2TileObjectCache().query() + .withIds( + ObjectID.STAR_SIZE_ONE_STAR, + ObjectID.STAR_SIZE_TWO_STAR, + ObjectID.STAR_SIZE_THREE_STAR, + ObjectID.STAR_SIZE_FOUR_STAR, + ObjectID.STAR_SIZE_FIVE_STAR, + ObjectID.STAR_SIZE_SIX_STAR, + ObjectID.STAR_SIZE_SEVEN_STAR, + ObjectID.STAR_SIZE_EIGHT_STAR, + ObjectID.STAR_SIZE_NINE_STAR + ) + .nearest(initialPlayerLocation, 10); + return hasStarModelChanged(starModel); } return false; } - private boolean hasStarGameObjectChanged(GameObject starObject) + private boolean hasStarModelChanged(Rs2TileObjectModel starModel) { - // If the GameObject does not exist anymore - if (starObject == null) + if (starModel == null) { return true; } - // If the GameObject has updated to a new tier - return currentStar.getObjectId() != starObject.getId(); + return currentStar.getObjectId() != starModel.getId(); } private Pickaxe getBestPickaxe(List items) diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java index 4975273797..547bd21569 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java @@ -121,7 +121,13 @@ public boolean hasRequirements() return false; } - boolean hasLineOfSight = Microbot.getClient().getLocalPlayer().getWorldArea().hasLineOfSightTo(Microbot.getClient().getTopLevelWorldView(), this.getWorldPoint()); + boolean hasLineOfSight = Boolean.TRUE.equals(Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (Microbot.getClient().getLocalPlayer() == null || Microbot.getClient().getLocalPlayer().getWorldArea() == null) + { + return false; + } + return Microbot.getClient().getLocalPlayer().getWorldArea().hasLineOfSightTo(Microbot.getClient().getTopLevelWorldView(), this.getWorldPoint()); + }).orElse(false)); switch (this) { case CRAFTING_GUILD: diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java new file mode 100644 index 0000000000..b03c1c3b2b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java @@ -0,0 +1,31 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; + +/** + * Config for Sisyphus: Infernal Pact — Yama's Lair stepping-stone automation. + */ +@ConfigGroup("sisyphusinfernalpact") +public interface SisyphusInfernalPactConfig extends Config { + + @ConfigSection( + name = "Yama Stepping Stones", + description = "Configure the Infernal Pact stepping-stone runner", + position = 0 + ) + String yamaSection = "yamaSection"; + + @ConfigItem( + keyName = "yamaStonesToStep", + name = "Stones to Step", + description = "Total safe stones to hop before stopping. Default 666 for the Demonic Pacts task.", + position = 0, + section = yamaSection + ) + @Range(min = 1, max = 5000) + default int yamaStonesToStep() { return 666; } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java new file mode 100644 index 0000000000..dbdc8d7082 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java @@ -0,0 +1,55 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; + +/** + * Sisyphus: Infernal Pact + * + * Standalone plugin that automates Yama's stepping-stone circuit. + * Enable near the first entry stone and it loops until the configured + * stone count is reached, never touching fire-laden tiles. + */ +@PluginDescriptor( + name = "[SIS] " + "Sisyphus: Infernal Pact", + description = "Automates Yama's stepping-stone circuit — avoids fire, loops the route, spam-clicks safe stones.", + tags = {"leagues", "sisyphus", "yama", "demonic", "pacts", "infernal", "stepping stones"}, + version = SisyphusInfernalPactPlugin.VERSION, + minClientVersion = "2.0.13", + isExternal = PluginConstants.IS_EXTERNAL, + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + authors = {"Sisyphus"} +) +@Slf4j +public class SisyphusInfernalPactPlugin extends Plugin { + + public static final String VERSION = "1.0.0"; + + @Inject private SisyphusInfernalPactConfig config; + @Inject private SisyphusInfernalPactScript script; + + @Provides + SisyphusInfernalPactConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(SisyphusInfernalPactConfig.class); + } + + @Override + protected void startUp() throws Exception { + log.info("[Sisyphus: Infernal Pact] Starting v{}", VERSION); + if (!script.isRunning()) { + script.run(config); + } + } + + @Override + protected void shutDown() throws Exception { + script.shutdown(); + log.info("[Sisyphus: Infernal Pact] Stopped."); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java new file mode 100644 index 0000000000..267413496b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java @@ -0,0 +1,64 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; +import net.runelite.client.plugins.microbot.sisyphusinfernalpact.engine.YamaEngine; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Execution loop for Sisyphus: Infernal Pact. + * Schedules the YamaEngine at 200-400 ms for near-instant stone hops. + */ +@Slf4j +public class SisyphusInfernalPactScript extends Script { + + private final YamaEngine yamaEngine = new YamaEngine(); + + public boolean run(SisyphusInfernalPactConfig config) { + Rs2Antiban.resetAntibanSettings(); + Rs2AntibanSettings.actionCooldownActive = false; + + mainScheduledFuture = scheduledExecutorService.schedule( + () -> scheduleNextTick(config), 0, TimeUnit.MILLISECONDS); + + return true; + } + + @Override + public void shutdown() { + super.shutdown(); + yamaEngine.reset(); + log.info("[Sisyphus: Infernal Pact] Script shutdown."); + } + + private void scheduleNextTick(SisyphusInfernalPactConfig config) { + int delayMs = ThreadLocalRandom.current().nextInt(200, 401); + + mainScheduledFuture = scheduledExecutorService.schedule(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (BreakHandlerScript.isBreakActive()) return; + + if (!yamaEngine.tick(config)) { + log.info("[Sisyphus: Infernal Pact] Engine stopped — shutting down."); + shutdown(); + return; + } + + } catch (Exception e) { + log.error("[Sisyphus: Infernal Pact] Error in tick: {}", e.getMessage(), e); + } finally { + if (isRunning()) { + scheduleNextTick(config); + } + } + }, delayMs, TimeUnit.MILLISECONDS); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java new file mode 100644 index 0000000000..cfe03fb8aa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java @@ -0,0 +1,306 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact.engine; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GroundObject; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.sisyphusinfernalpact.SisyphusInfernalPactConfig; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Yama's Lair stepping-stone engine — spam-click edition. + * + *

    + *
  • Only targets the 14 whitelisted stone waypoints.
  • + *
  • Skips any stone within {@value #FIRE_EXCLUSION_RADIUS} tiles of NPC + * ID {@value #FIRE_NPC_ID}.
  • + *
  • Never re-clicks the last {@value #RECENT_HISTORY} stones jumped to + * (prevents backtracking).
  • + *
  • No internal tick delay — fires as fast as the outer scheduler allows.
  • + *
+ */ +@Slf4j +public class YamaEngine { + + private static final int STONE_GROUND_ID = 13621; + private static final int FIRE_NPC_ID = 15608; + private static final int FIRE_EXCLUSION_RADIUS = 2; + private static final int STONE_SNAP_RADIUS = 2; + + /** How many recently-visited stones to remember (prevents backtracking). */ + private static final int RECENT_HISTORY = 3; + + private static final long JUMP_TIMEOUT_MS = 5_000; + + /** Fixed-coordinate loop — entry path followed by the circular loop. */ + private static final WorldPoint[] STONE_ROUTE = { + // ── Entry path ── + new WorldPoint(1486, 5598, 0), + new WorldPoint(1484, 5596, 0), + new WorldPoint(1481, 5597, 0), + new WorldPoint(1479, 5599, 0), + // ── Loop ── + new WorldPoint(1480, 5602, 0), + new WorldPoint(1478, 5602, 0), + new WorldPoint(1477, 5605, 0), + new WorldPoint(1479, 5605, 0), + new WorldPoint(1481, 5605, 0), + new WorldPoint(1481, 5608, 0), + new WorldPoint(1479, 5608, 0), + new WorldPoint(1477, 5608, 0), + new WorldPoint(1478, 5611, 0), + new WorldPoint(1480, 5611, 0), + }; + + public enum State { IDLE, SCANNING, JUMPING, COMPLETE, STUCK } + + @Getter private String status = "Idle"; + @Getter private boolean running = false; + @Getter private State state = State.IDLE; + + private int stonesJumped = 0; + private int targetStones = 666; + private long jumpStartTime = 0; + private WorldPoint lastPlayerLocation = null; + private int stuckCounter = 0; + + /** Rolling window of the last {@value #RECENT_HISTORY} stones jumped to. */ + private final Deque recentlyJumped = new ArrayDeque<>(); + + public void reset() { + status = "Idle"; + running = false; + state = State.IDLE; + stonesJumped = 0; + jumpStartTime = 0; + lastPlayerLocation = null; + stuckCounter = 0; + recentlyJumped.clear(); + } + + // ── Main tick ───────────────────────────────────────────────────────────── + + public boolean tick(SisyphusInfernalPactConfig config) { + running = true; + targetStones = config.yamaStonesToStep(); + + if (!Microbot.isLoggedIn()) { + status = "Waiting for login"; + return true; + } + + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) { + status = "Locating player"; + return true; + } + + if (stonesJumped >= targetStones) { + state = State.COMPLETE; + status = "Complete — " + stonesJumped + "/" + targetStones + " stones"; + return true; + } + + // ── Detect landing from a jump ── + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + if (state == State.JUMPING) { + if (lastPlayerLocation != null && !lastPlayerLocation.equals(playerLocation)) { + // We moved — record this tile in the history and increment counter + recordJump(playerLocation); + stonesJumped++; + status = "Jumped — " + stonesJumped + "/" + targetStones; + // No artificial delay — let the outer scheduler call us back + state = State.IDLE; + } else if (System.currentTimeMillis() - jumpStartTime > JUMP_TIMEOUT_MS) { + status = "Jump timed out — retrying"; + state = State.IDLE; + } else { + status = "Jumping... " + stonesJumped + "/" + targetStones; + } + } else { + status = "Moving — " + stonesJumped + "/" + targetStones; + } + lastPlayerLocation = playerLocation; + return true; + } + + // ── Player is still — find and click next safe stone immediately ── + state = State.SCANNING; + + List fireLocs = getAllFireLocations(); + Microbot.log("[Yama] fire=" + fireLocs.size() + + " recent=" + recentlyJumped.size() + + " jumped=" + stonesJumped + "/" + targetStones); + + GroundObject target = findBestSafeStone(playerLocation, fireLocs); + + if (target == null) { + stuckCounter++; + if (stuckCounter > 8) { + state = State.STUCK; + status = "Stuck — no safe route stone reachable"; + } else { + status = "Waiting for safe stone (fire=" + fireLocs.size() + ")"; + } + return true; + } + + stuckCounter = 0; + if (Rs2GameObject.interact(target)) { + state = State.JUMPING; + jumpStartTime = System.currentTimeMillis(); + lastPlayerLocation = playerLocation; + status = "→ " + target.getWorldLocation() + + " [" + stonesJumped + "/" + targetStones + "]"; + } else { + status = "Click failed — " + target.getWorldLocation(); + } + + return true; + } + + // ── Stone selection ─────────────────────────────────────────────────────── + + private GroundObject findBestSafeStone(WorldPoint playerLocation, List fireLocs) { + List allGround = getGroundObjects(); + + GroundObject best = null; + int bestDist = Integer.MAX_VALUE; + + for (WorldPoint waypoint : STONE_ROUTE) { + // Skip the tile the player is currently on + if (sameXY(waypoint, playerLocation)) continue; + + // Skip stones we jumped to recently (prevents backtracking) + if (isRecentlyJumped(waypoint)) continue; + + // Skip if within 2 tiles of any fire NPC + if (isNearFire(waypoint, fireLocs)) continue; + + // Require an actual GroundObject at this waypoint + GroundObject obj = findStoneNear(waypoint, allGround); + if (obj == null) continue; + + int dist = distance2D(waypoint, playerLocation); + if (dist < bestDist) { + bestDist = dist; + best = obj; + } + } + return best; + } + + private GroundObject findStoneNear(WorldPoint waypoint, List groundObjects) { + GroundObject closest = null; + int minDist = Integer.MAX_VALUE; + for (GroundObject obj : groundObjects) { + if (obj.getId() != STONE_GROUND_ID) continue; + int d = distance2D(obj.getWorldLocation(), waypoint); + if (d <= STONE_SNAP_RADIUS && d < minDist) { + minDist = d; + closest = obj; + } + } + return closest; + } + + private List getGroundObjects() { + try { + List list = Rs2GameObject.getGroundObjects(); + return list != null ? list : Collections.emptyList(); + } catch (Exception e) { + log.debug("[Yama] Error getting ground objects", e); + return Collections.emptyList(); + } + } + + // ── Recent-stone tracking ───────────────────────────────────────────────── + + private void recordJump(WorldPoint location) { + // Keep only the most recent RECENT_HISTORY positions + recentlyJumped.addFirst(location); + while (recentlyJumped.size() > RECENT_HISTORY) { + recentlyJumped.removeLast(); + } + } + + private boolean isRecentlyJumped(WorldPoint waypoint) { + for (WorldPoint p : recentlyJumped) { + if (sameXY(p, waypoint)) return true; + } + return false; + } + + // ── Fire NPC detection ──────────────────────────────────────────────────── + + /** + * Dual-source fire NPC location collection. + * Queries both the Microbot NPC cache (manual ID filter) and the raw + * {@code Client.getNpcs()} list so fast-spawning fire effects are never missed. + */ + private List getAllFireLocations() { + List locations = new ArrayList<>(); + + // Source 1: Microbot NPC cache + try { + List cached = Microbot.getRs2NpcCache().query().toList(); + if (cached != null) { + for (Rs2NpcModel npc : cached) { + if (npc != null && npc.getId() == FIRE_NPC_ID) { + WorldPoint loc = npc.getWorldLocation(); + if (loc != null) locations.add(loc); + } + } + } + } catch (Exception e) { + log.debug("[Yama] NPC cache error", e); + } + + // Source 2: Raw client NPC list + try { + List clientNpcs = Microbot.getClient().getNpcs(); + if (clientNpcs != null) { + for (NPC npc : clientNpcs) { + if (npc != null && npc.getId() == FIRE_NPC_ID) { + WorldPoint loc = npc.getWorldLocation(); + if (loc != null) locations.add(loc); + } + } + } + } catch (Exception e) { + log.debug("[Yama] Client NPC list error", e); + } + + return locations; + } + + private boolean isNearFire(WorldPoint target, List fireLocs) { + for (WorldPoint fire : fireLocs) { + if (distance2D(fire, target) <= FIRE_EXCLUSION_RADIUS) return true; + } + return false; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private static boolean sameXY(WorldPoint a, WorldPoint b) { + if (a == null || b == null) return false; + return a.getX() == b.getX() && a.getY() == b.getY(); + } + + private static int distance2D(WorldPoint a, WorldPoint b) { + if (a == null || b == null) return Integer.MAX_VALUE; + return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java index d313c2fde9..40d93f5093 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class SlayerPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private SlayerConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java index 8a6354e58d..ce2bfe254f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java @@ -2,9 +2,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.TileObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; @@ -16,8 +14,9 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2Cannon; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2LootEngine; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; @@ -29,9 +28,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.npc.MonsterLocation; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -347,9 +344,9 @@ private void handleGettingTaskState(boolean hasTask, String taskName) { if (distance <= 5) { // We're at the master, interact to get task - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Assignment")) { + if (masterNpc.click("Assignment")) { log.info("Requesting task from {}", master.getName()); sleepUntil(Rs2Dialogue::isInDialogue, 3000); } @@ -539,9 +536,9 @@ private void handleSkippingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Rewards")) { + if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to skip task (attempt {})", skipAttemptCounter + 1); sleepUntil(() -> Rs2Dialogue.isInDialogue() || Rs2Widget.isWidgetVisible(SLAYER_REWARDS_GROUP_ID, 0), 3000); } @@ -618,7 +615,7 @@ private void handleRestoringAtPohState() { } private boolean isInPoh() { - return Rs2GameObject.findObjectById(HOUSE_PORTAL_ID) != null; + return Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_ID).nearest() != null; } private boolean isFullPrayer() { @@ -680,8 +677,8 @@ private boolean teleportToHouse() { private boolean useRejuvenationPool() { for (int poolId : REJUVENATION_POOL_IDS) { - if (Rs2GameObject.exists(poolId)) { - return Rs2GameObject.interact(poolId, "Drink"); + if (Microbot.getRs2TileObjectCache().query().withId(poolId).nearest() != null) { + return Microbot.getRs2TileObjectCache().query().interact(poolId, "Drink"); } } return false; @@ -707,7 +704,7 @@ private void leaveHouse() { } // Fallback to house portal exit - if (Rs2GameObject.interact(HOUSE_PORTAL_ID, "Enter")) { + if (Microbot.getRs2TileObjectCache().query().interact(HOUSE_PORTAL_ID, "Enter")) { log.info("Leaving house via portal (no teleport options found)"); } } @@ -719,13 +716,11 @@ private void leaveHouse() { private boolean teleportViaPortalNexus() { // Try to find any tier of portal nexus for (int nexusId : PORTAL_NEXUS_IDS) { - if (Rs2GameObject.exists(nexusId)) { - // Try direct Grand Exchange teleport option first - if (Rs2GameObject.interact(nexusId, "Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().withId(nexusId).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(nexusId, "Grand Exchange")) { return true; } - // Some nexus configurations might use different option names - if (Rs2GameObject.interact(nexusId, "Varrock Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().interact(nexusId, "Varrock Grand Exchange")) { return true; } } @@ -739,8 +734,8 @@ private boolean teleportViaPortalNexus() { */ private boolean teleportViaMountedGlory() { for (int gloryId : MOUNTED_GLORY_IDS) { - if (Rs2GameObject.exists(gloryId)) { - if (Rs2GameObject.interact(gloryId, "Edgeville")) { + if (Microbot.getRs2TileObjectCache().query().withId(gloryId).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(gloryId, "Edgeville")) { return true; } } @@ -753,8 +748,8 @@ private boolean teleportViaMountedGlory() { * @return true if interaction was successful, false if mounted wealth not found */ private boolean teleportViaMountedWealth() { - if (Rs2GameObject.exists(MOUNTED_WEALTH_ID)) { - if (Rs2GameObject.interact(MOUNTED_WEALTH_ID, "Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().withId(MOUNTED_WEALTH_ID).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(MOUNTED_WEALTH_ID, "Grand Exchange")) { return true; } } @@ -1049,9 +1044,9 @@ private void handleBlockingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Rewards")) { + if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to block task (attempt {})", blockAttemptCounter + 1); sleepUntil(() -> Rs2Dialogue.isInDialogue() || Rs2Widget.isWidgetVisible(SLAYER_REWARDS_GROUP_ID, 0), 3000); } @@ -1603,39 +1598,33 @@ private void handleSwappingSpellbookState() { } // Find and interact with the occult altar - TileObject occultAltar = findOccultAltar(); + Rs2TileObjectModel occultAltar = findOccultAltar(); if (occultAltar != null) { - // Try right-click option first (e.g., "Ancient Magicks", "Lunar", "Standard") String altarOption = getAltarOptionForSpellbook(requiredSpellbook); log.info("Attempting occult altar right-click option: '{}'", altarOption); - if (Rs2GameObject.interact(occultAltar, altarOption)) { - // Wait briefly for either direct spellbook switch or a dialog to open + if (occultAltar.click(altarOption)) { sleepUntil(() -> Rs2Magic.isSpellbook(requiredSpellbook) || Rs2Dialogue.hasSelectAnOption(), 3000); - // Check if the spellbook already switched (right-click option worked directly) if (Rs2Magic.isSpellbook(requiredSpellbook)) { log.info("Successfully switched to {} spellbook via right-click", requiredSpellbook); finishSpellbookSwap(); return; } - // Right-click option may have opened a dialog - handle it if (Rs2Dialogue.hasSelectAnOption() && handleSpellbookWidget(requiredSpellbook)) { return; } } else { - // Right-click option didn't match - try left-click "Venerate" which opens dialog log.info("Right-click option '{}' not found, trying Venerate", altarOption); - if (Rs2GameObject.interact(occultAltar, "Venerate")) { + if (occultAltar.click("Venerate")) { sleepUntil(Rs2Dialogue::hasSelectAnOption, 3000); if (!handleSpellbookWidget(requiredSpellbook)) { log.warn("Venerate used but could not handle spellbook dialog"); } } else { - // Last resort: plain interact log.info("Trying plain interact on occult altar"); - Rs2GameObject.interact(occultAltar); + occultAltar.click(); sleepUntil(Rs2Dialogue::hasSelectAnOption, 3000); handleSpellbookWidget(requiredSpellbook); } @@ -1771,9 +1760,9 @@ private String getWidgetTextForSpellbook(Rs2Spellbook spellbook) { /** * Finds the occult altar in the POH. */ - private TileObject findOccultAltar() { + private Rs2TileObjectModel findOccultAltar() { for (int altarId : OCCULT_ALTAR_IDS) { - TileObject altar = Rs2GameObject.findObjectById(altarId); + Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withId(altarId).nearest(); if (altar != null) { return altar; } @@ -2704,7 +2693,7 @@ private void handleCombat() { Rs2NpcModel anyAttacker = findAnyNpcAttackingUs(); if (anyAttacker != null) { log.info("Fallback: Attacking {} that is attacking us (at task location)", anyAttacker.getName()); - if (Rs2Npc.interact(anyAttacker, "Attack")) { + if (anyAttacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } } @@ -2720,7 +2709,7 @@ private void handleCombat() { Rs2NpcModel superior = findNearbySuperior(); if (superior != null) { log.info("Superior monster detected: {}! Attacking.", superior.getName()); - if (Rs2Npc.interact(superior, "Attack")) { + if (superior.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2730,10 +2719,9 @@ private void handleCombat() { // Check if we're already in combat with a living target Actor currentInteracting = Rs2Player.getInteracting(); if (currentInteracting != null) { - // Verify the target is actually alive - don't idle on dead NPCs boolean targetAlive = true; - if (currentInteracting instanceof Rs2NpcModel) { - Rs2NpcModel npc = (Rs2NpcModel) currentInteracting; + if (currentInteracting instanceof net.runelite.api.NPC) { + net.runelite.api.NPC npc = (net.runelite.api.NPC) currentInteracting; targetAlive = !npc.isDead() && npc.getHealthRatio() != 0; } if (targetAlive) { @@ -2750,7 +2738,7 @@ private void handleCombat() { Rs2NpcModel attacker = findNpcAttackingUs(targetMonsters); if (attacker != null) { log.info("Retaliating against target monster: {}", attacker.getName()); - if (Rs2Npc.interact(attacker, "Attack")) { + if (attacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2763,7 +2751,7 @@ private void handleCombat() { Rs2NpcModel anyAttacker = findAnyNpcAttackingUs(); if (anyAttacker != null) { log.info("Fallback: Retaliating against {} (at task location, not in target list)", anyAttacker.getName()); - if (Rs2Npc.interact(anyAttacker, "Attack")) { + if (anyAttacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2775,17 +2763,18 @@ private void handleCombat() { // Find attackable NPCs matching target monsters // Prioritize NPCs that are already attacking us (interacting with player) - List attackableNpcs = Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> targetMonsters.stream() + List attackableNpcs = Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> matchesTargetMonster(npc.getName(), monster))) - .filter(npc -> taskDestination == null || + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) + .toListOnClientThread() + .stream() .sorted(Comparator - // First priority: NPCs already attacking us (interacting with player) .comparingInt((Rs2NpcModel npc) -> npc.getInteracting() == Microbot.getClient().getLocalPlayer() ? 0 : 1) - // Second priority: closest distance .thenComparingInt(npc -> Rs2Player.getWorldLocation().distanceTo(npc.getWorldLocation()))) .collect(Collectors.toList()); @@ -2794,10 +2783,13 @@ private void handleCombat() { log.debug("No attackable slayer monsters found nearby matching: {}", targetMonsters); // Debug: log nearby NPCs to help diagnose - List nearbyNpcNames = Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> taskDestination == null || + List nearbyNpcNames = Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) + .toListOnClientThread() + .stream() .map(Rs2NpcModel::getName) .distinct() .collect(Collectors.toList()); @@ -2847,7 +2839,7 @@ private void handleCombat() { // Attack the first NPC (prioritizes those attacking us, then closest) Rs2NpcModel target = attackableNpcs.get(0); - if (Rs2Npc.interact(target, "Attack")) { + if (target.click("Attack")) { log.info("Attacking {}", target.getName()); sleepUntil(Rs2Player::isInteracting, 1000); } @@ -2939,8 +2931,9 @@ private int getHopWorld() { * Checks if any NPC is currently attacking the player. */ private boolean isBeingAttacked() { - return Rs2Npc.getNpcsForPlayer() - .anyMatch(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .count() > 0; } /** @@ -2948,15 +2941,14 @@ private boolean isBeingAttacked() { * Used when we're "in combat" (being attacked) but not attacking back. */ private Rs2NpcModel findNpcAttackingUs(List targetMonsters) { - return Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> targetMonsters.stream() + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null) + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> matchesTargetMonster(npc.getName(), monster))) - .filter(npc -> taskDestination == null || + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) - .findFirst() - .orElse(null); + .first(); } /** @@ -2964,12 +2956,11 @@ private Rs2NpcModel findNpcAttackingUs(List targetMonsters) { * Used as a fallback when variant/target list doesn't match. */ private Rs2NpcModel findAnyNpcAttackingUs() { - return Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> !npc.isDead()) - .findFirst() - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null) + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> !npc.isDead()) + .first(); } /** @@ -3033,14 +3024,15 @@ private void handleAoeCombat() { return; } - List nearbyMonsters = Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> targetMonsters.stream() + List nearbyMonsters = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> npc.getName() != null) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> npc.getName().equalsIgnoreCase(monster))) - .filter(npc -> !npc.isDead()) - .filter(npc -> taskDestination == null || + .where(npc -> !npc.isDead()) + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) - .collect(Collectors.toList()); + .toList(); if (nearbyMonsters.isEmpty()) { log.debug("No target monsters found nearby for AoE combat"); @@ -3081,7 +3073,7 @@ private void handleAoeCombat() { if (activeJsonProfile.shouldUseGoading() && bestTarget != null) { log.info("Attacking {} to trigger goading aggro ({} monsters nearby)", bestTarget.getName(), nearbyMonsters.size()); - if (Rs2Npc.interact(bestTarget, "Attack")) { + if (bestTarget.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -3102,7 +3094,7 @@ private void handleAoeCombat() { log.info("Attacking {} with autocast {} ({} monsters stacked)", bestTarget.getName(), spell.name(), maxStackedCount); - if (Rs2Npc.interact(bestTarget, "Attack")) { + if (bestTarget.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } } @@ -3209,11 +3201,14 @@ private String convertTaskNameToNpcName(String taskName) { * @return The superior NPC if found, null otherwise */ private Rs2NpcModel findNearbySuperior() { - return Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> SUPERIOR_MONSTERS.contains(npc.getName())) - .filter(npc -> taskDestination == null || - npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) // Slightly larger radius for superiors + return Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> SUPERIOR_MONSTERS.contains(npc.getName())) + .where(npc -> taskDestination == null || + npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) + .toList() + .stream() .min(Comparator.comparingInt(npc -> Rs2Player.getWorldLocation().distanceTo(npc.getWorldLocation()))) .orElse(null); @@ -3950,24 +3945,22 @@ private boolean fireCannon() { */ private boolean handleCannonPickup() { // Check if cannon still exists - var cannon = Rs2GameObject.findObject("Dwarf multicannon", true, 50, false, Rs2Player.getWorldLocation()); + var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 50).nearestOnClientThread(); if (cannon == null) { log.info("Cannon already picked up or not found"); - return true; // Cannon doesn't exist, we're done + return true; } WorldPoint cannonLocation = cannon.getWorldLocation(); int distance = Rs2Player.getWorldLocation().distanceTo(cannonLocation); - // If too far, walk to cannon first if (distance > 5) { log.info("Walking to cannon to pick it up (distance: {})", distance); Rs2Walker.walkTo(cannonLocation, 2); - return false; // Still in progress + return false; } - // Try to pick up cannon - if (Rs2GameObject.interact(cannon, "Pick-up")) { + if (cannon.click("Pick-up")) { log.info("Picking up cannon..."); sleepUntil(() -> !isCannonPlacedNearby(), 5000); @@ -3990,7 +3983,8 @@ private boolean handleCannonPickup() { * @return true if pickup was initiated successfully */ private boolean pickupCannon() { - if (Rs2GameObject.interact("Dwarf multicannon", "Pick-up")) { + var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").nearestOnClientThread(); + if (cannon != null && cannon.click("Pick-up")) { log.info("Picking up cannon..."); return true; } @@ -4002,7 +3996,7 @@ private boolean pickupCannon() { * @return true if cannon object is found nearby */ private boolean isCannonPlacedNearby() { - return Rs2GameObject.findObject("Dwarf multicannon", true, 10, false, Rs2Player.getWorldLocation()) != null; + return Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 10).nearestOnClientThread() != null; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java index 0d52fb6d15..62ad937f1c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java @@ -2,13 +2,13 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.events.NpcDespawned; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.slayer.PrayerFlickStyle; import net.runelite.client.plugins.microbot.slayer.SlayerConfig; import net.runelite.client.plugins.microbot.slayer.SlayerPrayer; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -99,7 +99,9 @@ public void onGameTick() { } // Update NPC snapshot - npcsRef.set(Rs2Npc.getNpcsForPlayer().collect(Collectors.toList())); + npcsRef.set(Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .toList()); // Remove monsters that no longer exist currentMonstersAttackingUsRef.updateAndGet(monsters -> { diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java index 3ac43ca716..d0c1b7dae5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java @@ -1,7 +1,7 @@ package net.runelite.client.plugins.microbot.slayer.combat; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcStats; /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java index 48180a70e9..c26b3e216f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class AutoSmeltingPlugin extends Plugin { - public static final String version = "1.0.3"; + public static final String version = "1.0.4"; @Inject private AutoSmeltingConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java index 9747f70b27..195f2cf6c7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.smelting; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -10,13 +9,12 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; -import java.text.MessageFormat; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -36,8 +34,13 @@ public boolean run(AutoSmeltingConfig config) { try { if (!super.run()) return; if (!Microbot.isLoggedIn()) return; - if (config.SELECTED_BAR_TYPE().getRequiredSmithingLevel() > Rs2Player.getBoostedSkillLevel(Skill.SMITHING)) { - Microbot.showMessage("Your smithing level isn't high enough for " + config.SELECTED_BAR_TYPE().toString()); + int currentSmithing = Rs2Player.getRealSkillLevel(Skill.SMITHING); + // Skill reads return 0 transiently during login / region load. + if (currentSmithing <= 0) return; + int requiredSmithing = config.SELECTED_BAR_TYPE().getRequiredSmithingLevel(); + if (currentSmithing < requiredSmithing) { + Microbot.log("Smithing " + currentSmithing + " is below the " + requiredSmithing + + " required for " + config.SELECTED_BAR_TYPE() + ". Shutting down."); super.shutdown(); return; } @@ -62,6 +65,10 @@ public boolean run(AutoSmeltingConfig config) { if (!Rs2Player.isInMemberWorld()) { Rs2Bank.depositAll(); } else if (Rs2Player.isMember()) Rs2Bank.depositAllExcept(coalBag); + // depositAll fires a menu click and returns before the next game tick + // processes it. Wait for slots to free so the upcoming withdrawX isn't + // blocked by Rs2Bank's "inventory full of the wrong item" guard. + sleepUntil(() -> !Rs2Inventory.isFull(), 3000); if (config.SELECTED_BAR_TYPE().getId() == ItemID.IRON_BAR && Rs2Bank.hasItem(ItemID.RING_OF_FORGING) && !Rs2Equipment.isWearing(ItemID.RING_OF_FORGING)) { Rs2Bank.withdrawAndEquip(ItemID.RING_OF_FORGING); return; @@ -72,7 +79,7 @@ public boolean run(AutoSmeltingConfig config) { Rs2Bank.withdrawAndEquip(ItemID.GAUNTLETS_OF_GOLDSMITHING); return; } - if (selectedBar != i && (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES) || Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) && (!Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE) ||!Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE))) { + if (selectedBar != i && (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES) || Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) && !Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE) && !Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES)) { if (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) { Rs2Bank.withdrawAndEquip(ItemID.SMITHING_UNIFORM_GLOVES_ICE); return; @@ -108,46 +115,40 @@ public boolean run(AutoSmeltingConfig config) { withdrawRightAmountOfMaterials(config); return; } - GameObject oneClickFurnace = Rs2GameObject.findObject("furnace", true, 20, false, initialPlayerLocation); - if (oneClickFurnace != null) { - if (Rs2Bank.isOpen()){ - Rs2Bank.closeBank(); - sleepUntil(() -> !Rs2Bank.isOpen(), 1000); - } - Rs2GameObject.interact(oneClickFurnace, "smelt"); - sleepUntil(Rs2Player::isMoving, 1000); - sleepUntil(() -> !Rs2Player.isMoving(), 4000); - Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Widget.clickWidget(config.SELECTED_BAR_TYPE().getName()); - Rs2Widget.sleepUntilHasNotWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); + if (Rs2Bank.isOpen()) { + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 1500); return; } - // walk to the initial position (near furnace) - if (initialPlayerLocation.distanceTo(Rs2Player.getWorldLocation()) > 4) { - if (Rs2Bank.isOpen()) - Rs2Bank.closeBank(); - Rs2Walker.walkTo(initialPlayerLocation, 4); + // Run the lookup on the client thread so each getName() resolves in-place + // instead of round-tripping through ClientThread.invoke per scene object. + Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query() + .withNameContains("furnace") + .nearestOnClientThread(initialPlayerLocation, 20); + + if (furnace == null) { + if (initialPlayerLocation.distanceTo(Rs2Player.getWorldLocation()) > 4) { + Rs2Walker.walkTo(initialPlayerLocation, 4); + } else { + Microbot.status = "AutoSmelting: no furnace within 20 tiles of start — stand near a furnace and restart"; + } return; } - // interact with the furnace until the smelting dialogue opens in chat, click the selected bar icon - GameObject furnace = Rs2GameObject.findObject("furnace",true,20,false,initialPlayerLocation); - if (furnace != null) { - Rs2GameObject.interact(furnace, "smelt"); - Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Widget.clickWidget(config.SELECTED_BAR_TYPE().getName()); - Rs2Widget.sleepUntilHasNotWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - } + furnace.click("smelt"); + sleepUntil(Rs2Player::isMoving, 1000); + sleepUntil(() -> !Rs2Player.isMoving(), 6000); + Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); + Rs2Widget.clickWidget(config.SELECTED_BAR_TYPE().getName()); + Rs2Widget.sleepUntilHasNotWidgetText("What would you like to smelt?", 270, 5, false, 4000); + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); } catch (Exception ex) { - System.out.println(ex.getMessage()); + Microbot.logStackTrace("AutoSmeltingScript", ex); } - }, 0, 100, TimeUnit.MILLISECONDS); + }, 0, 600, TimeUnit.MILLISECONDS); return true; } @@ -180,16 +181,16 @@ private void withdrawRightAmountOfMaterials(AutoSmeltingConfig config) { ? config.SELECTED_BAR_TYPE().getWithdrawalsWithCoalBag(Rs2Inventory.capacity()).get(requiredMaterials.getKey()) : config.SELECTED_BAR_TYPE().maxBarsForFullInventory() * amountForOne; if (!Rs2Bank.hasBankItem(name, totalAmount, true)) { - Microbot.showMessage(MessageFormat.format("Required Materials not in bank. You need {1} {0}.", name, totalAmount)); - super.shutdown(); + Microbot.log("Bank lacks " + totalAmount + " " + name + ". Shutting down."); + shutdown(); + return; } Rs2Bank.withdrawX(name, totalAmount, true); sleepUntil(() -> Rs2Inventory.hasItemAmount(name, totalAmount, false, true), 3500); - - // Exit if we did not end up finding it. + // Withdraw missed the verify window — let the next tick retry instead of bailing. if (!Rs2Inventory.hasItemAmount(name, totalAmount, false, true)) { - Microbot.showMessage("Could not find item in bank."); - shutdown(); + Microbot.log("Withdraw of " + totalAmount + " " + name + " didn't settle in 3.5s; retrying next tick."); + return; } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java b/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java index f3f01e1619..5c659c28ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java @@ -5,7 +5,6 @@ import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -18,7 +17,7 @@ public class StoneChestThieverScript extends Script { private static void handleThieving() { if (Rs2Player.isAnimating()) return; // Chest ID - Rs2GameObject.interact(34429, "Picklock"); + Microbot.getRs2TileObjectCache().query().interact(34429, "Picklock"); Rs2Player.waitForAnimation(600); Rs2Player.waitForXpDrop(Skill.THIEVING); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java index 23f96b5bd2..c7c0e2a30e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java @@ -29,7 +29,7 @@ @Slf4j public class SulphurNaguaPlugin extends Plugin { - public static final String version = "2.0.0"; + public static final String version = "2.0.1"; @Inject SulphurNaguaScript sulphurNaguaScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java index c2263a186b..0dc4cb2623 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java @@ -16,11 +16,9 @@ import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -361,14 +359,14 @@ private void getSupplies(int itemID, int requiredAmount) { Rs2Dialogue.clickOption("Take herblore supplies."); } else if (!Rs2Player.isAnimating()) { int SUPPLY_CRATE_ID = 51371; - Rs2GameObject.interact(SUPPLY_CRATE_ID, "Take herblore supplies"); + Microbot.getRs2TileObjectCache().query().interact(SUPPLY_CRATE_ID, "Take herblore supplies"); } sleep(300, 500); } } else { if (Rs2Player.isAnimating()) return; int GRUB_SAPLING_ID = 51365; - if (Rs2GameObject.interact(GRUB_SAPLING_ID, "Collect-from")) { + if (Microbot.getRs2TileObjectCache().query().interact(GRUB_SAPLING_ID, "Collect-from")) { sleepUntil(() -> Rs2Inventory.count(itemID) >= requiredAmount || Rs2Inventory.isFull(), 15000); if (Rs2Player.isAnimating() && Rs2Inventory.count(itemID) >= requiredAmount) { Rs2Walker.walkTo(Rs2Player.getWorldLocation()); @@ -481,7 +479,7 @@ private void handleGettingRunecraftingXp(SulphurNaguaConfig config) { return; } - var eytallali = Rs2Npc.getNpc(EYTALLALI_ID); + var eytallali = Microbot.getRs2NpcCache().query().withId(EYTALLALI_ID).nearest(); if (eytallali == null) { Microbot.log("Waiting for Eytallali to appear..."); sleep(600, 1000); @@ -493,7 +491,7 @@ private void handleGettingRunecraftingXp(SulphurNaguaConfig config) { return; } - if (Rs2Inventory.useItemOnNpc(SULPHUROUS_ESSENCE_ID, eytallali)) { + if (Rs2Inventory.useItemOnNpc(SULPHUROUS_ESSENCE_ID, EYTALLALI_ID)) { Microbot.log("Exchanging essence..."); sleepUntil(Rs2Dialogue::isInDialogue, 5000); } @@ -523,13 +521,13 @@ private void handleFighting(SulphurNaguaConfig config) { if (needsNewTarget) { if (getNaguaCombatArea() != null && getNaguaCombatArea().contains(Rs2Player.getWorldLocation())) { - var nagua = Rs2Npc.getNpcs("Sulphur Nagua") - .filter(n -> !n.isDead()) - .findFirst() - .orElse(null); + var nagua = Microbot.getRs2NpcCache().query() + .withName("Sulphur Nagua") + .where(n -> !n.isDead()) + .firstOnClientThread(); if (nagua != null) { - if (Rs2Npc.attack(nagua)) { + if (nagua.click("Attack")) { sleepUntil(Rs2Player::isInCombat, 3000); totalNaguaKills++; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java index 4d7f47ca62..91e8b30103 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class SummerGardenPlugin extends Plugin { - public final static String version = "1.0.2"; + public final static String version = "1.0.3"; @Inject private Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java index bbff051f6b..d032a58674 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java @@ -10,11 +10,10 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.summergarden.ElementalCollisionDetector; import net.runelite.client.plugins.microbot.summergarden.SummerGardenConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; + import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -170,7 +169,7 @@ private boolean goInsideHouse() { if (!isInHouseArea()) { // The door is closed and the player is outside the house area. Open it and wait until it's open. if (getHouseDoor() != null) { - Rs2GameObject.interact(OBJECT_HOUSE_DOOR_CLOSED); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_HOUSE_DOOR_CLOSED); sleepUntil(() -> getHouseDoor() == null, 10000); } @@ -188,13 +187,13 @@ private boolean goInsideHouse() { private void exitGarden() { // Can't find the fountain? - if (Rs2GameObject.findObjectById(12941) == null) { + if (Microbot.getRs2TileObjectCache().query().withId(12941).nearest() == null) { return; } // If the player is still in the garden then click the fountain to exit and wait until the player is teleported out. if (isInGarden()) { - Rs2GameObject.interact(12941); + Microbot.getRs2TileObjectCache().query().interact(12941); sleepUntil(() -> isInHouseArea(), 10000); } @@ -228,7 +227,7 @@ private void doMaze(SummerGardenConfig config) { // Click tree if (WORLD_POINT_MAZE_STARTING_LOCATION.equals(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()))) { if (config.waitForOneClick() || ElementalCollisionDetector.getTicksUntilStart() == 0) { - Rs2GameObject.interact(OBJECT_SUMMER_TREE); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_SUMMER_TREE); sleepUntil(() -> Rs2Player.isMoving()); sleepUntil(() -> !Rs2Player.isMoving(), 30000); sleepUntilOnClientThread(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getY() < 5481); @@ -243,9 +242,9 @@ private void doMaze(SummerGardenConfig config) { } // Click Gate - TileObject gate = Rs2GameObject.findObjectById(ObjectID.GATE_11987); + Rs2TileObjectModel gate = Microbot.getRs2TileObjectCache().query().withId(ObjectID.GATE_11987).nearest(); if (gate != null) { - Rs2GameObject.interact(gate); + gate.click(); sleepUntil(Rs2Player::isMoving); sleepUntil(() -> !Rs2Player.isMoving()); sleepUntilOnClientThread(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().equals(WORLD_POINT_MAZE_STARTING_LOCATION)); @@ -268,12 +267,12 @@ private void completeAndReset() { // The door is closed and the player is inside the house area. Open it and wait until it's open. if (getHouseDoor() != null && isInHouseArea()) { - Rs2GameObject.interact(OBJECT_HOUSE_DOOR_CLOSED); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_HOUSE_DOOR_CLOSED); sleepUntil(() -> getHouseDoor() == null, 10000); } // Check if the player has arrived at Osman's location, if not then walk there. - var npcOsman = Rs2Npc.getNpc(NPC_NAME_OSMAN); + var npcOsman = Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).nearestOnClientThread(); if (npcOsman == null) { var osmanLocalLocation = LocalPoint.fromWorld(Microbot.getClient(), osmanLocation); if (osmanLocalLocation != null) { @@ -285,7 +284,7 @@ private void completeAndReset() { // Interact with Osman. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_OSMAN)) { - Rs2Npc.interact(NPC_NAME_OSMAN, "Talk-to"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).interact("Talk-to")); sleepUntil(() -> Rs2Player.getInteracting() != null, 2000); return; } @@ -350,7 +349,7 @@ private void handleReturnToHouse() { // Interact with shelf to get beer glass. while (Rs2Inventory.count("Beer glass") < 25) { - Rs2GameObject.interact(OBJECT_BEER_GLASS_SHELF); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_BEER_GLASS_SHELF); sleep(3000); } @@ -363,14 +362,14 @@ private void handleReturnToHouse() { } // Check if the player has arrived at the Apprentice's location. - var npcApprentice = Rs2Npc.getNpc(NPC_NAME_APPRENTICE); + var npcApprentice = Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).nearestOnClientThread(); if (npcApprentice == null) { return; } // Interact with the apprentice. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_APPRENTICE)) { - Rs2Npc.interact(NPC_NAME_APPRENTICE, "Teleport"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).interact("Teleport")); sleepUntil(() -> isInGarden(), 10000); } @@ -398,7 +397,7 @@ private void makeLastJuice() { if (!Rs2Inventory.hasItemAmount("Beer glass", 1, false, true)) { if (Rs2Inventory.count("Beer glass") == 0) { - Rs2GameObject.interact(OBJECT_BEER_GLASS_SHELF); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_BEER_GLASS_SHELF); sleepUntil(() -> Rs2Inventory.hasItem("beer glass"), 5000); sleep(3000); } @@ -420,7 +419,7 @@ private void makeLastJuice() { } while (Rs2Inventory.count("Summer sq'irkjuice") != 26) { - Rs2GroundItem.pickup("Summer sq'irkjuice", 200); + Microbot.getRs2TileItemCache().query().withName("Summer sq'irkjuice").interact("Take"); sleepUntil(() -> Rs2Inventory.hasItemAmount("Summer sq'irk", 26, false, true), 5000); sleep(3000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java index 3c6dbad641..ee8e8d4454 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java @@ -9,7 +9,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -58,14 +58,14 @@ public Dimension render(Graphics2D graphics) { // Render NPC overlays if the list is not null if (npcList != null) { for (Rs2NpcModel npc : npcList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getRuneliteNpc().getWorldLocation()); + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " tiles"); } } if (ammoList != null) { for (Rs2NpcModel npc : ammoList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getRuneliteNpc().getWorldLocation()); + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " " + Text.removeTags(npc.getName())); } @@ -151,16 +151,14 @@ private void renderGameObject(Graphics2D graphics, GameObject object, Color colo // Add this method to render overlays for NPCs private void renderNpcOverlay(Graphics2D graphics, Rs2NpcModel npc, Color color, String label) { - if (npc == null || npc.getConvexHull() == null) { + if (npc == null || npc.getNpc() == null || npc.getNpc().getConvexHull() == null) { return; } - // Draw the NPC outline - Shape npcHull = npc.getConvexHull(); + Shape npcHull = npc.getNpc().getConvexHull(); OverlayUtil.renderPolygon(graphics, npcHull, color); - // Draw the label above the NPC - Point textLocation = npc.getCanvasTextLocation(graphics, label, npc.getLogicalHeight() + 40); + Point textLocation = npc.getNpc().getCanvasTextLocation(graphics, label, npc.getNpc().getLogicalHeight() + 40); if (textLocation != null) { OverlayUtil.renderTextLocation(graphics, textLocation, label, Color.WHITE); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java index 8e3eda85bf..9f882747ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java @@ -17,8 +17,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.tempoross.enums.HarpoonType; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayManager; import java.util.regex.Pattern; @@ -118,7 +117,7 @@ public void onGameTick(GameTick e) { TemporossScript.updateAmmoCrateData(); TemporossScript.updateLastWalkPath(); - Rs2NpcModel doubleFishingSpot = Rs2Npc.getNpc(NpcID.FISHING_SPOT_10569); + Rs2NpcModel doubleFishingSpot = Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_10569).nearest(); if (TemporossScript.state == State.INITIAL_COOK && doubleFishingSpot != null) { TemporossScript.state = TemporossScript.state.next; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java index 3ee9bc51fe..2bab58e767 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java @@ -20,8 +20,8 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -131,8 +131,8 @@ private boolean hasHarpoon() { private void determineWorkArea() { if (workArea == null) { - Rs2NpcModel forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); - Rs2NpcModel ammoCrate = Rs2Npc.getNearestNpcWithAction("Fill"); + Rs2NpcModel forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); + Rs2NpcModel ammoCrate = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill")).nearest(); if (forfeitNpc == null || ammoCrate == null) { log("Can't find forfeit NPC or ammo crate"); @@ -150,21 +150,22 @@ private void determineWorkArea() { private void finishGame() { Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); - Rs2NpcModel exitNpc = Rs2Npc.getNpcs() - .filter(value -> value.getComposition() != null - && value.getComposition().getActions() != null - && Arrays.asList(value.getComposition().getActions()).contains("Leave")) + Rs2NpcModel exitNpc = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Leave")) + .toList().stream() .min(Comparator.comparingInt(value -> playerLocation.distanceToPath(value.getWorldLocation()))) .orElse(null); if (exitNpc != null) { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); if (emptyBucketCount > 0) { - if(Rs2GameObject.interact(41004, "Fill-bucket")) + if(Microbot.getRs2TileObjectCache().query().interact(41004, "Fill-bucket")) sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) < 1); } - if (Rs2Npc.interact(exitNpc, "Leave")) { + if (exitNpc.click("Leave")) { reset(); sleepUntil(() -> !isInMinigame(), 15000); BreakHandlerScript.setLockState(false); @@ -190,9 +191,9 @@ private void reset(){ public void handleForfeit() { if ((INTENSITY >= 94 && state == State.THIRD_COOK)) { - var forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); + var forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); if (forfeitNpc != null) { - if (Rs2Npc.interact(forfeitNpc, "Forfeit")) { + if (forfeitNpc.click("Forfeit")) { sleepUntil(() -> !isInMinigame(), 15000); reset(); BreakHandlerScript.setLockState(false); @@ -202,9 +203,9 @@ public void handleForfeit() { } private void forfeit() { - var forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); + var forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); if (forfeitNpc != null) { - if (Rs2Npc.interact(forfeitNpc, "Forfeit")) { + if (forfeitNpc.click("Forfeit")) { sleepUntil(() -> !isInMinigame(), 15000); reset(); BreakHandlerScript.setLockState(false); @@ -282,7 +283,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getHarpoonCrate(), "Take")) + if (workArea.getHarpoonCrate() != null && workArea.getHarpoonCrate().click("Take")) { log("Taking harpoon"); sleepUntil(this::hasHarpoon, 10000); @@ -306,7 +307,7 @@ private void fetchMissingItems() } sleepUntil(() -> Rs2Inventory.count(item -> item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets(),() -> { - if (Rs2GameObject.interact(workArea.getBucketCrate(), "Take")) { + if (workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { log("Taking buckets"); Rs2Inventory.waitForInventoryChanges(3000); }},10000,300); @@ -327,7 +328,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getPump(), "Use")) + if (workArea.getPump() != null && workArea.getPump().click("Use")) { log("Filling buckets"); sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0, 10000); @@ -346,7 +347,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getRopeCrate(), "Take")) + if (workArea.getRopeCrate() != null && workArea.getRopeCrate().click("Take")) { log("Taking rope"); sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); @@ -365,7 +366,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getHammerCrate(), "Take")) + if (workArea.getHammerCrate() != null && workArea.getHammerCrate().click("Take")) { log("Taking hammer"); sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); @@ -374,7 +375,7 @@ private void fetchMissingItems() } private boolean isOnStartingBoat() { - TileObject startingLadder = Rs2GameObject.findObjectById(ObjectID.ROPE_LADDER_41305); + Rs2TileObjectModel startingLadder = Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPE_LADDER_41305).nearest(); if (startingLadder == null) { log("Failed to find starting ladder"); return false; @@ -389,7 +390,7 @@ private void handleEnterMinigame() { if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { return; } - TileObject startingLadder = Rs2GameObject.findObjectById(ObjectID.ROPE_LADDER_41305); + Rs2TileObjectModel startingLadder = Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPE_LADDER_41305).nearest(); if (startingLadder == null) { log("Failed to find starting ladder"); return; @@ -397,17 +398,17 @@ private void handleEnterMinigame() { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); // If we are east of the ladder, interact with it to get on the boat if (!isOnStartingBoat()) { - if (Rs2GameObject.interact(startingLadder, ((emptyBucketCount > 0 && temporossConfig.solo()) || !temporossConfig.solo()) ? "Climb" : "Solo-start")) { + if (startingLadder.click(((emptyBucketCount > 0 && temporossConfig.solo()) || !temporossConfig.solo()) ? "Climb" : "Solo-start")) { BreakHandlerScript.setLockState(true); sleepUntil(() -> (isOnStartingBoat() || isInMinigame()), 15000); return; } } - TileObject waterPump = Rs2GameObject.findObjectById(ObjectID.WATER_PUMP_41000); + Rs2TileObjectModel waterPump = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WATER_PUMP_41000).nearest(); if (waterPump != null && emptyBucketCount > 0) { - if (Rs2GameObject.interact(waterPump, "Use")) { + if (waterPump.click("Use")) { Rs2Player.waitForAnimation(5000); } } @@ -446,10 +447,10 @@ public static void handleWidgetInfo() { } public static void updateFireData(){ - List allFires = Rs2Npc - .getNpcs(npc -> Arrays.asList(npc.getComposition().getActions()).contains("Douse")) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List allFires = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Douse")) + .toList(); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); sortedFires = allFires.stream() .filter(y -> playerLocation.distanceToPath(y.getWorldLocation()) < 35) @@ -472,22 +473,22 @@ public static void updateCloudData(){ // update ammo crate data public static void updateAmmoCrateData(){ - List ammoCrates = Rs2Npc - .getNpcs() - .filter(npc -> Arrays.asList(npc.getComposition().getActions()).contains("Fill")) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4) - .filter(npc -> !inCloud(npc.getWorldLocation(),2)) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List ammoCrates = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && !inCloud(npc.getWorldLocation(), 2)) + .toList(); TemporossOverlay.setAmmoList(ammoCrates); } public static void updateFishSpotData(){ // if double fishing spot is present, prioritize it - fishSpots = Rs2Npc.getNpcs() - .filter(npc -> npc.getId() == NpcID.FISHING_SPOT_10569 || npc.getId() == NpcID.FISHING_SPOT_10568 || npc.getId() == NpcID.FISHING_SPOT_10565) - .filter(npc -> !inCloud(npc.getRuneliteNpc().getWorldLocation(),2)) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.rangePoint) <= 20) + fishSpots = Microbot.getRs2NpcCache().query() + .withIds(NpcID.FISHING_SPOT_10569, NpcID.FISHING_SPOT_10568, NpcID.FISHING_SPOT_10565) + .where(npc -> !inCloud(npc.getWorldLocation(), 2) + && npc.getWorldLocation().distanceTo(workArea.rangePoint) <= 20) + .toList().stream() .sorted(Comparator .comparingInt(npc -> npc.getId() == NpcID.FISHING_SPOT_10569 ? 0 : 1)) .collect(Collectors.toList()); @@ -519,11 +520,11 @@ private void handleFires() { return; } if (Rs2Player.isInteracting()) { - if (Objects.equals(Rs2Player.getInteracting(), fire)) { + if (Objects.equals(Rs2Player.getInteracting(), fire.getNpc())) { return; } } - if (Rs2Npc.interact(fire, "Douse")) { + if (fire.click("Douse")) { log("Dousing fire"); sleepUntil(() -> !Rs2Player.isInteracting(), 3000); return; @@ -535,12 +536,12 @@ private void handleDamagedMast() { if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; - TileObject damagedMast = workArea.getBrokenMast(); + Rs2TileObjectModel damagedMast = workArea.getBrokenMast(); if(damagedMast == null) return; if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedMast.getWorldLocation())) <= 5) { sleep(600); - if (Rs2GameObject.interact(damagedMast, "Repair")) { + if (damagedMast.click("Repair")) { log("Repairing mast"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); } @@ -551,12 +552,12 @@ private void handleDamagedTotem() { if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; - TileObject damagedTotem = workArea.getBrokenTotem(); + Rs2TileObjectModel damagedTotem = workArea.getBrokenTotem(); if(damagedTotem == null) return; if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedTotem.getWorldLocation())) <= 5) { sleep(600); - if (Rs2GameObject.interact(damagedTotem, "Repair")) { + if (damagedTotem.click("Repair")) { log("Repairing totem"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); } @@ -564,7 +565,7 @@ private void handleDamagedTotem() { } private void handleTether() { - TileObject tether = workArea.getClosestTether(); + Rs2TileObjectModel tether = workArea.getClosestTether(); if (tether == null) { return; } @@ -572,10 +573,10 @@ private void handleTether() { ShortestPathPlugin.exit(); Rs2Walker.setTarget(null); String action = TemporossPlugin.incomingWave ? "Tether" : "Untether"; - Rs2Camera.turnTo(tether); + Rs2Camera.turnTo(tether.getLocalLocation()); if (action.equals("Tether")) { - if (Rs2GameObject.interact(tether, action)) { + if (tether.click(action)) { log(action + "ing"); sleepUntil(() -> TemporossPlugin.isTethered == TemporossPlugin.incomingWave, 3500); } @@ -588,7 +589,7 @@ private void handleTether() { } private void handleStateLoop() { - temporossPool = Rs2Npc.getNpcs().filter(npc -> npc.getId() == NpcID.SPIRIT_POOL).min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))).orElse(null); + temporossPool = Microbot.getRs2NpcCache().query().withId(NpcID.SPIRIT_POOL).toList().stream().min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))).orElse(null); boolean doubleFishingSpot = !fishSpots.isEmpty() && fishSpots.get(0).getId() == NpcID.FISHING_SPOT_10569; if (TemporossScript.state == State.INITIAL_COOK && doubleFishingSpot) { @@ -646,8 +647,8 @@ private void handleMainLoop() { .orElse(null); if (safeFishSpot != null) { - Rs2Camera.turnTo(safeFishSpot); - Rs2Npc.interact(safeFishSpot, "Harpoon"); + Rs2Camera.turnTo(safeFishSpot.getNpc()); + safeFishSpot.click("Harpoon"); Microbot.log("Moved to a " + (safeFishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot."); @@ -679,8 +680,8 @@ private void handleMainLoop() { } } } - Rs2Camera.turnTo(fishSpot); - Rs2Npc.interact(fishSpot, "Harpoon"); + Rs2Camera.turnTo(fishSpot.getNpc()); + fishSpot.click("Harpoon"); log("Interacting with " + (fishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot"); Rs2Player.waitForWalking(2000); } else { @@ -710,16 +711,15 @@ private void handleMainLoop() { case THIRD_COOK: isFilling = false; int rawFishCount = Rs2Inventory.count(ItemID.RAW_HARPOONFISH); - TileObject range = workArea != null ? workArea.getRange() : null; + Rs2TileObjectModel range = workArea != null ? workArea.getRange() : null; if (range != null && rawFishCount > 0) { if(Rs2Player.isInteracting()) { - if (Objects.equals(Rs2Player.getInteracting(), range)) - return; + return; } if (Rs2Player.isMoving() || Rs2Player.getAnimation() == AnimationID.COOKING_RANGE) { return; } - Rs2GameObject.interact(range, "Cook-at"); + range.click("Cook-at"); log("Interacting with range"); sleepUntil(Rs2Player::isAnimating, 5000); } else if (range == null) { @@ -734,13 +734,13 @@ private void handleMainLoop() { case EMERGENCY_FILL: case SECOND_FILL: case INITIAL_FILL: - List ammoCrates = Rs2Npc - .getNpcs() - .filter(npc -> npc.getComposition() != null && npc.getComposition().getActions() != null && Arrays.asList(npc.getComposition().getActions()).contains("Fill")) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4) - .filter(npc -> !inCloud(npc.getWorldLocation(),1)) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List ammoCrates = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && !inCloud(npc.getWorldLocation(), 1)) + .toList(); WorldPoint fillPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); if (inCloud(fillPlayerLoc,5) && !isFilling) { @@ -762,8 +762,8 @@ private void handleMainLoop() { log("In cloud, walking to safe point"); Rs2NpcModel ammoCrate = ammoCrates.stream() .max(Comparator.comparingInt(value -> new Rs2WorldPoint(value.getWorldLocation()).distanceToPath(fillPlayerLoc))).orElse(null); - Rs2Camera.turnTo(ammoCrate); - Rs2Npc.interact(ammoCrate, "Fill"); + Rs2Camera.turnTo(ammoCrate.getNpc()); + ammoCrate.click("Fill"); log("Switching ammo crate"); Rs2Player.waitForWalking(5000); isFilling = true; @@ -787,8 +787,8 @@ private void handleMainLoop() { return; } } - Rs2Camera.turnTo(ammoCrate.getActor()); - Rs2Npc.interact(ammoCrate, "Fill"); + Rs2Camera.turnTo(ammoCrate.getNpc()); + ammoCrate.click("Fill"); log("Interacting with ammo crate"); Rs2Inventory.waitForInventoryChanges(5000); isFilling = true; @@ -822,7 +822,7 @@ private void handleMainLoop() { // Log message when special energy is below 100% log("Special energy is below 100%, not using harpoon special attack."); } - Rs2Npc.interact(temporossPool, "Harpoon"); + temporossPool.click("Harpoon"); log("Harpooning Tempoross"); Rs2Player.waitForWalking(2000); } else { @@ -913,7 +913,7 @@ public boolean fightFiresInPath(WorldPoint location) { // Filter fires that are actually on the path. List firesInPath = sortedFires.stream() - .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getWorldArea().contains(pathPoint))) + .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))) .collect(Collectors.toList()); if (firesInPath.isEmpty()) { @@ -926,7 +926,7 @@ public boolean fightFiresInPath(WorldPoint location) { } for (Rs2NpcModel fire : firesInPath) { - if (Rs2Npc.interact(fire, "Douse")) { + if (fire.click("Douse")) { log("Dousing fire in path (mass world mode)"); sleepUntil(Rs2Player::isInteracting, 2000); sleepUntil(() -> !Rs2Player.isInteracting(), 10000); @@ -934,7 +934,7 @@ public boolean fightFiresInPath(WorldPoint location) { } // Return true if sortedFires does not contain any fires in the path. - return sortedFires.stream().noneMatch(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getWorldArea().contains(pathPoint))); + return sortedFires.stream().noneMatch(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))); } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java index f13b225997..23038a16f7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java @@ -2,11 +2,10 @@ import net.runelite.api.NullObjectID; import net.runelite.api.ObjectID; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; public class TemporossWorkArea @@ -54,76 +53,56 @@ public TemporossWorkArea(WorldPoint exitNpc, boolean isWest) } } - public TileObject getBucketCrate() + public Rs2TileObjectModel getBucketCrate() { - return Rs2GameObject.findObject(ObjectID.BUCKETS, bucketPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.BUCKETS).within(bucketPoint, 2).nearest(); } - public TileObject getPump() + public Rs2TileObjectModel getPump() { - return Rs2GameObject.findObject(ObjectID.WATER_PUMP_41000, pumpPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.WATER_PUMP_41000).within(pumpPoint, 2).nearest(); } - public TileObject getRopeCrate() + public Rs2TileObjectModel getRopeCrate() { - return Rs2GameObject.findObject(ObjectID.ROPES, ropePoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPES).within(ropePoint, 2).nearest(); } - public TileObject getHammerCrate() + public Rs2TileObjectModel getHammerCrate() { - return Rs2GameObject.findObject(ObjectID.HAMMERS_40964, hammerPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.HAMMERS_40964).within(hammerPoint, 2).nearest(); } - public TileObject getHarpoonCrate() + public Rs2TileObjectModel getHarpoonCrate() { - return Rs2GameObject.findObject(ObjectID.HARPOONS, harpoonPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.HARPOONS).within(harpoonPoint, 2).nearest(); } - public TileObject getMast() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),mastPoint).stream().findFirst().orElse(null); - TileObject mast = Rs2GameObject.findGameObjectByLocation(mastPoint); - if (mast != null && (mast.getId() == NullObjectID.NULL_41352 || mast.getId() == NullObjectID.NULL_41353)) { - return mast; - } - return null; + public Rs2TileObjectModel getMast() { + Rs2TileObjectModel mast = Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41352, NullObjectID.NULL_41353).within(mastPoint, 2).nearest(); + return mast; } - public TileObject getBrokenMast() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),mastPoint).stream().findFirst().orElse(null); - TileObject mast = Rs2GameObject.findGameObjectByLocation(mastPoint); - if (mast != null && (mast.getId() == ObjectID.DAMAGED_MAST_40996 || mast.getId() == ObjectID.DAMAGED_MAST_40997)) - return mast; - - return null; + public Rs2TileObjectModel getBrokenMast() { + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_MAST_40996, ObjectID.DAMAGED_MAST_40997).within(mastPoint, 2).nearest(); } - public TileObject getTotem() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),totemPoint).stream().findFirst().orElse(null); - TileObject totem = Rs2GameObject.findGameObjectByLocation(totemPoint); - if (totem != null && (totem.getId() == NullObjectID.NULL_41355 || totem.getId() == NullObjectID.NULL_41354)) { - return totem; - } - return null; + public Rs2TileObjectModel getTotem() { + return Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41355, NullObjectID.NULL_41354).within(totemPoint, 2).nearest(); } - public TileObject getBrokenTotem() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),totemPoint).stream().findFirst().orElse(null); - TileObject totem = Rs2GameObject.findGameObjectByLocation(totemPoint); - if (totem != null && (totem.getId() == ObjectID.DAMAGED_TOTEM_POLE || totem.getId() == ObjectID.DAMAGED_TOTEM_POLE_41011)) - return totem; - - return null; + public Rs2TileObjectModel getBrokenTotem() { + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_TOTEM_POLE, ObjectID.DAMAGED_TOTEM_POLE_41011).within(totemPoint, 2).nearest(); } - public TileObject getRange() + public Rs2TileObjectModel getRange() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),rangePoint).stream().findFirst().orElse(null); - return Rs2GameObject.findObject(ObjectID.SHRINE_41236, rangePoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHRINE_41236).within(rangePoint, 2).nearest(); } - public TileObject getClosestTether() { - TileObject mast = getMast(); - TileObject totem = getTotem(); + public Rs2TileObjectModel getClosestTether() { + Rs2TileObjectModel mast = getMast(); + Rs2TileObjectModel totem = getTotem(); if (mast == null) { return totem; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java index c88e971aa5..d0e99a53bf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java @@ -25,8 +25,8 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.models.RS2Item; @@ -329,7 +329,7 @@ private State getCurrentState() { } // delayed door closing logic - List doors = getDoors(Rs2Player.getWorldLocation(), DOOR_CHECK_RADIUS); + List doors = getDoors(Rs2Player.getWorldLocation(), DOOR_CHECK_RADIUS); if (doors.isEmpty()) { DOOR_TIMER.unset(); } else if (DOOR_TIMER.isSet()) { @@ -431,20 +431,13 @@ private void loop() { final int id = getMostExpensiveGroundItemId(); if (id == -1) return; if (Rs2Inventory.isFull()) dropAllExceptImportant(); - final RS2Item item = Arrays.stream(Rs2GroundItem.getAll(50)) - .filter(rs2Item -> rs2Item.getItem().getId() == id) - .findFirst().orElse(null); + final Rs2TileItemModel item = Microbot.getRs2TileItemCache().query().withId(id).within(50).nearest(); if (item == null) { log.warn("Loot Item is null"); return; } - final Tile tile = item.getTile(); - if (tile == null) { - log.warn("Loot Tile is null"); - return; - } - walkTo("Walk to loot", item.getTile().getWorldLocation(), 1); - Rs2GroundItem.interact(item); + walkTo("Walk to loot", item.getWorldLocation(), 1); + item.click("Take"); return; case ESCAPE: WorldPoint escape = null; @@ -760,23 +753,23 @@ private String toString(WorldPoint point) { return "(" + point.getX() + "," + point.getY() + "," + point.getPlane() + ")"; } - private List getDoors(WorldPoint wp, int radius) { + private List getDoors(WorldPoint wp, int radius) { if (wp == null) return Collections.emptyList(); final Rs2WorldPoint rs2Wp = new Rs2WorldPoint(wp); - // this take 1.5s off client thread - return Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2GameObject.getAll( - o -> { - ObjectComposition comp = Rs2GameObject.convertToObjectComposition(o); + return Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getRs2TileObjectCache().query() + .within(wp, radius) + .where(o -> { + var comp = o.getObjectComposition(); if (comp == null || !Arrays.asList(comp.getActions()).contains("Close")) return false; - - final WorldPoint objWp = o.getWorldLocation(); - return rs2Wp.distanceToPath(objWp) < Integer.MAX_VALUE; - }, wp, radius - )).orElse(Collections.emptyList()); + return rs2Wp.distanceToPath(o.getWorldLocation()) < Integer.MAX_VALUE; + }) + .toList() + ).orElse(Collections.emptyList()); }; private boolean closeNearbyDoor(int radius) { - List doors; + List doors; int doorCount = 0; while (!(doors = getDoors(Rs2Player.getWorldLocation(), radius)).isEmpty()) { if (doorCount >= 3) { @@ -785,12 +778,12 @@ private boolean closeNearbyDoor(int radius) { } final WorldPoint myLoc = Rs2Player.getWorldLocation(); if (myLoc == null) return false; - final TileObject door = doors.stream() + final Rs2TileObjectModel door = doors.stream() .min(Comparator.comparingInt(d -> d.getWorldLocation().distanceTo(myLoc))) .orElseThrow(); final WorldPoint doorWp = door.getWorldLocation(); - if (!Rs2GameObject.interact(door, "Close")) return false; + if (!door.click("Close")) return false; if (door.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 1) { if (!sleepUntilWithInterrupt(() -> Rs2Player.isMoving() || Rs2Player.isStunned(), 1_200)) return false; if (Rs2Player.isStunned()) return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java index d526900535..06bf74e516 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java @@ -25,7 +25,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(-2)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(-2)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java index 81f2b8af73..f6de7cb649 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java @@ -23,7 +23,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dy(-2)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dy(-2)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java index 0f166948da..fb9b852e26 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java @@ -1,11 +1,11 @@ package net.runelite.client.plugins.microbot.thievingstalls.model; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; public class BotApi { @@ -16,14 +16,14 @@ public boolean walkTo(final WorldPoint worldPoint) return Rs2Walker.walkTo(worldPoint, 0); } - public GameObject getGameObject(final int id, final WorldPoint worldPoint) + public Rs2TileObjectModel getGameObject(final int id, final WorldPoint worldPoint) { - return Rs2GameObject.findObject(id, worldPoint); + return Microbot.getRs2TileObjectCache().query().withId(id).nearest(worldPoint, 5); } - public void steal(final GameObject gameObject) + public void steal(final Rs2TileObjectModel gameObject) { - Rs2GameObject.interact(gameObject, STEAL_ACTION); + gameObject.click(STEAL_ACTION); } public void dropAll(int... ids) diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java index c60d12fb65..d95ab333b6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java @@ -1,12 +1,13 @@ package net.runelite.client.plugins.microbot.thievingstalls.model; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Gembag; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -40,7 +41,7 @@ public void thieve() { return; } - final GameObject stall = Rs2GameObject.getGameObject(STALL_ID, SAFESPOT, 2); + final Rs2TileObjectModel stall = Microbot.getRs2TileObjectCache().query().withId(STALL_ID).within(SAFESPOT, 2).nearest(); if (stall == null) { boolean started = Microbot.hopToWorld(Login.getRandomWorld(Rs2Player.isMember())); if (started) { @@ -49,7 +50,7 @@ public void thieve() { return; } - if (!Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(stall), "Steal-from")) { + if (!Rs2GameObject.hasAction(stall.getObjectComposition(), "Steal-from")) { boolean started = Microbot.hopToWorld(Login.getRandomWorld(Rs2Player.isMember())); if (started) { Global.sleepUntil(Microbot::isLoggedIn, 15000); @@ -57,7 +58,7 @@ public void thieve() { return; } - Rs2GameObject.interact(stall, "Steal-from"); + stall.click("Steal-from"); Rs2Player.waitForXpDrop(Skill.THIEVING); if (Rs2Gembag.hasGemBag()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java index 88167bab25..2baee78d38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java @@ -22,7 +22,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(1)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(1)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java index 0ea3511e4b..3f8a8d0770 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java @@ -23,7 +23,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, new WorldPoint(3269, 3410, 0)); + final var stall = botApi.getGameObject(STALL_ID, new WorldPoint(3269, 3410, 0)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java index ad095c91e9..680f96ce84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java @@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Item; import net.runelite.api.events.ChatMessage; -import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.ItemContainerChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; @@ -15,7 +14,6 @@ import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmMaterial; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; -import net.runelite.client.plugins.microbot.tithefarming.models.TitheFarmPlant; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; @@ -42,7 +40,7 @@ @Slf4j public class TitheFarmingPlugin extends Plugin { - final static String version = "1.1.5"; + final static String version = "1.1.13"; @Inject public TitheFarmingConfig config; @@ -81,15 +79,6 @@ protected void shutDown() { overlayManager.remove(titheFarmOverlay); } - @Subscribe - public void onGameObjectSpawned(GameObjectSpawned event) { - for (TitheFarmPlant plant : net.runelite.client.plugins.microbot.tithefarming.TitheFarmingScript.plants) { - if (event.getGameObject().getWorldLocation().equals(plant.getGameObject().getWorldLocation())) { - plant.setGameObject(event.getGameObject()); - } - } - } - @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { if (TitheFarmMaterial.getSeedForLevel() != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java index 6433d7d66d..a50a7edff9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java @@ -1,13 +1,12 @@ package net.runelite.client.plugins.microbot.tithefarming; -import net.runelite.api.TileObject; -import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.AnimationID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmLanes; @@ -15,7 +14,6 @@ import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; import net.runelite.client.plugins.microbot.tithefarming.models.TitheFarmPlant; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -170,6 +168,11 @@ public void init(TitheFarmingConfig config) { public boolean run(TitheFarmingConfig config) { + init = true; + plants = new ArrayList<>(); + state = STARTING; + allPlanted = false; + Microbot.log("Tithe farming script started"); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn()) return; @@ -254,7 +257,7 @@ public boolean run(TitheFarmingConfig config) { } if (config.enableDebugging() && plants.stream().anyMatch(x -> x.getGameObject() == null)) { - Microbot.showMessage("There is an empty plant gameobject!"); + Microbot.log("There is an empty plant gameobject!"); } } catch (Exception ex) { @@ -307,7 +310,11 @@ private void coreLoop(TitheFarmingConfig config) { // if we finished planting all patches, don't plant anything until we finish harvesting // otherwise if we lag/miss a plant, and it dies, we will keep trying to plant seeds and mess up the loop - if (plants.stream().noneMatch(TitheFarmPlant::isEmptyPatch)) + // require data for every patch before flipping allPlanted: when the cache hasn't loaded + // the patches yet, isEmptyPatch() returns false for all of them and noneMatch would + // erroneously trip even though we have planted nothing. + if (plants.stream().allMatch(p -> p.getGameObject() != null) + && plants.stream().noneMatch(TitheFarmPlant::isEmptyPatch)) allPlanted = true; if (plant == null && plants.stream().anyMatch(TitheFarmPlant::isValidToHarvest)) { @@ -324,7 +331,8 @@ private void coreLoop(TitheFarmingConfig config) { final TitheFarmPlant finalPlant = plant; WorldPoint corePlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - if (plant.getGameObject().getWorldLocation().distanceTo2D(corePlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { + Rs2TileObjectModel plantModel = plant.getGameObject(); + if (plantModel == null || plantModel.getWorldLocation().distanceTo2D(corePlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { WorldPoint w = WorldPoint.fromRegion(corePlayerLoc.getRegionID(), plant.regionX, plant.regionY, @@ -398,23 +406,32 @@ private boolean validateSeedsAndPatches() { private static void clickPatch(TitheFarmPlant plant) { - WorldPoint patchPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - WorldPoint worldPoint = WorldPoint.fromRegion(patchPlayerLoc.getRegionID(), - plant.regionX, - plant.regionY, - Microbot.getClient().getTopLevelWorldView().getPlane()); - - Rs2GameObject.interact(worldPoint); + Rs2TileObjectModel model = plant.getGameObject(); + if (model == null) return; + model.click(); } private static void clickPatch(TitheFarmPlant plant, String action) { - WorldPoint patchPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - WorldPoint worldPoint = WorldPoint.fromRegion(patchPlayerLoc.getRegionID(), - plant.regionX, - plant.regionY, - Microbot.getClient().getTopLevelWorldView().getPlane()); + Rs2TileObjectModel model = plant.getGameObject(); + if (model == null) return; + model.click(action); + } - Rs2GameObject.interact(worldPoint, action); + private static boolean interactWithObject(int id, String action) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query() + .withId(id) + .nearest(); + if (model == null) { + Microbot.log("Object id " + id + " not in scene"); + return false; + } + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.distanceTo(model.getWorldLocation()) > 51) { + Microbot.log("Object id " + id + " is " + playerLoc.distanceTo(model.getWorldLocation()) + " tiles away, walking..."); + Rs2Walker.walkTo(model.getWorldLocation()); + return false; + } + return action == null ? model.click() : model.click(action); } private static void DropFertiliser() { @@ -430,7 +447,10 @@ private void refillWaterCans(TitheFarmingConfig config) { if (gricollerCanCharges < config.gricollerCanRefillThreshold()) { walkToBarrel(); Rs2Inventory.interact(ItemID.ZEAH_WATERINGCAN, "Use"); - Rs2GameObject.interact("Water barrel"); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withName("Water barrel") + .nearestOnClientThread(); + if (barrel != null) barrel.click(); sleepUntil(Rs2Player::isAnimating, 10000); } else { state = PLANTING_SEEDS; @@ -438,7 +458,10 @@ private void refillWaterCans(TitheFarmingConfig config) { } else if (TitheFarmMaterial.hasWateringCanToBeFilled()) { walkToBarrel(); Rs2Inventory.interact(TitheFarmMaterial.getWateringCanToBeFilled(), "Use"); - Rs2GameObject.interact(ObjectID.WATER_BARREL1, "Use"); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.WATER_BARREL1) + .nearest(); + if (barrel != null) barrel.click(); sleepUntil(() -> Rs2Inventory.hasItemAmount(ItemID.WATERING_CAN_8, WATERING_CANS_AMOUNT), 60000); } else { state = PLANTING_SEEDS; @@ -446,13 +469,17 @@ private void refillWaterCans(TitheFarmingConfig config) { } private void walkToBarrel() { - final TileObject gameObject = Rs2GameObject.findObjectById(ObjectID.WATER_BARREL1); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.WATER_BARREL1) + .nearest(); + if (barrel == null) return; + WorldPoint barrelLoc = barrel.getWorldLocation(); WorldPoint barrelPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - if (gameObject.getWorldLocation().distanceTo2D(barrelPlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { - Rs2Walker.walkMiniMap(gameObject.getWorldLocation(), 1); + if (barrelLoc.distanceTo2D(barrelPlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { + Rs2Walker.walkMiniMap(barrelLoc, 1); sleepUntil(Rs2Player::isMoving); } - sleepUntil(() -> gameObject.getWorldLocation().distanceTo2D(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())) < DISTANCE_THRESHOLD_MINIMAP_WALK); + sleepUntil(() -> barrelLoc.distanceTo2D(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())) < DISTANCE_THRESHOLD_MINIMAP_WALK); } private void checkGricollerCharges() { @@ -465,7 +492,7 @@ private void takeSeeds() { Rs2Inventory.drop(TitheFarmMaterial.getSeedForLevel().getName()); sleep(400, 600); } - Rs2GameObject.interact(ObjectID.TITHE_PLANT_SEED_TABLE); + interactWithObject(ObjectID.TITHE_PLANT_SEED_TABLE, null); boolean result = Rs2Widget.sleepUntilHasWidget(TitheFarmMaterial.getSeedForLevel().getName()); if (!result) return; Rs2Keyboard.keyPress(TitheFarmMaterial.getSeedForLevel().getOption()); @@ -477,15 +504,14 @@ private void takeSeeds() { } private void enter() { - WallObject farmDoor = Rs2GameObject.getWallObject(FARM_DOOR); - Rs2GameObject.interact(farmDoor); + interactWithObject(FARM_DOOR, null); sleepUntil(this::isInMinigame); } private boolean depositSack() { if (Rs2Inventory.hasItem(TitheFarmMaterial.getSeedForLevel().getFruitId())) { Microbot.log("Storing fruits into sack for experience..."); - Rs2GameObject.interact(ObjectID.TITHE_SACK_OF_FRUIT_EMPTY); + interactWithObject(ObjectID.TITHE_SACK_OF_FRUIT_EMPTY, null); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); return true; @@ -494,8 +520,7 @@ private boolean depositSack() { } private void leave() { - WallObject farmDoor = Rs2GameObject.getWallObject(FARM_DOOR); - Rs2GameObject.interact(farmDoor); + interactWithObject(FARM_DOOR, null); sleepUntil(() -> !Rs2Inventory.hasItem(FERTILISER), 8000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java index 8f85558a60..8ce1e8b508 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java @@ -26,13 +26,13 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.tithefarming.TitheFarmingScript; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmMaterial; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.tithefarm.TitheFarmPlantState; import java.time.Duration; @@ -40,8 +40,6 @@ import java.util.Arrays; import java.util.Objects; -import static net.runelite.api.coords.WorldPoint.fromRegion; - public class TitheFarmPlant { private static final Duration PLANT_TIME = Duration.ofMinutes(1); @@ -56,22 +54,66 @@ public class TitheFarmPlant { @Getter private final TitheFarmPlantState state; - @Getter - @Setter - private TileObject gameObject; - public int regionX; public int regionY; public TitheFarmPlant(int regionX, int regionY, int index) { this.planted = Instant.now(); this.state = TitheFarmPlantState.UNWATERED; - this.gameObject = Rs2GameObject.findGameObjectByLocation(fromRegion(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getRegionID()), regionX, regionY, 0)); this.regionX = regionX; this.regionY = regionY; this.index = index; } + public Rs2TileObjectModel getGameObject() { + // The hardcoded lane definitions are off by 1 from the actual instance patch + // tiles (verified via the agent server: lane defines (35,25) but the cache has + // a tithe patch at local (34,24), and so on for every patch). Allow a 1-tile + // tolerance match so the lookup is robust to this without rewriting all four + // lane lists. Patches are 3 tiles apart so the tolerance can't match the + // wrong neighbour. + return Microbot.getRs2TileObjectCache().query() + .where(o -> { + if (!isTithePatchId(o.getId())) return false; + WorldPoint wp = o.getWorldLocation(); + if (wp == null) return false; + int dx = Math.abs(wp.getRegionX() - regionX); + int dy = Math.abs(wp.getRegionY() - regionY); + return dx <= 1 && dy <= 1; + }) + .first(); + } + + private static boolean isTithePatchId(int id) { + switch (id) { + case ObjectID.HOSIDIUS_TITHE_EMPTY: + case ObjectID.HOSIDIUS_TITHE_A_1_DRY: + case ObjectID.HOSIDIUS_TITHE_A_2_DRY: + case ObjectID.HOSIDIUS_TITHE_A_3_DRY: + case ObjectID.HOSIDIUS_TITHE_A_4: + case ObjectID.HOSIDIUS_TITHE_A_1_WET: + case ObjectID.HOSIDIUS_TITHE_A_2_WET: + case ObjectID.HOSIDIUS_TITHE_A_3_WET: + case ObjectID.HOSIDIUS_TITHE_B_1_DRY: + case ObjectID.HOSIDIUS_TITHE_B_2_DRY: + case ObjectID.HOSIDIUS_TITHE_B_3_DRY: + case ObjectID.HOSIDIUS_TITHE_B_4: + case ObjectID.HOSIDIUS_TITHE_B_1_WET: + case ObjectID.HOSIDIUS_TITHE_B_2_WET: + case ObjectID.HOSIDIUS_TITHE_B_3_WET: + case ObjectID.HOSIDIUS_TITHE_C_1_DRY: + case ObjectID.HOSIDIUS_TITHE_C_2_DRY: + case ObjectID.HOSIDIUS_TITHE_C_3_DRY: + case ObjectID.HOSIDIUS_TITHE_C_4: + case ObjectID.HOSIDIUS_TITHE_C_1_WET: + case ObjectID.HOSIDIUS_TITHE_C_2_WET: + case ObjectID.HOSIDIUS_TITHE_C_3_WET: + return true; + default: + return false; + } + } + public int[] expectedPatchGameObject() { if (Objects.requireNonNull(TitheFarmingScript.state) == TitheFarmState.PLANTING_SEEDS) { return new int[]{ObjectID.HOSIDIUS_TITHE_EMPTY, ObjectID.HOSIDIUS_TITHE_A_1_DRY, ObjectID.HOSIDIUS_TITHE_B_1_DRY, ObjectID.HOSIDIUS_TITHE_C_1_DRY}; @@ -108,35 +150,51 @@ public int expectedHarvestObject() { } public boolean isEmptyPatch() { - return gameObject.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; } public boolean isEmptyPatchOrSeedling() { - return Arrays.stream(expectedPatchGameObject()).anyMatch(id -> id == gameObject.getId()); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int objId = obj.getId(); + return Arrays.stream(expectedPatchGameObject()).anyMatch(id -> id == objId); } public boolean isValidToWater() { - return Arrays.stream(expectedWateredObject()).anyMatch(id -> id == gameObject.getId()) || isStage1() || isStage2(); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int objId = obj.getId(); + return Arrays.stream(expectedWateredObject()).anyMatch(id -> id == objId) || isStage1() || isStage2(); } public boolean isValidToHarvest() { - return gameObject.getId() == expectedHarvestObject(); + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == expectedHarvestObject(); } public boolean isStage1() { - return getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_A_2_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_B_2_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_C_2_DRY; + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int id = obj.getId(); + return id == ObjectID.HOSIDIUS_TITHE_A_2_DRY + || id == ObjectID.HOSIDIUS_TITHE_B_2_DRY + || id == ObjectID.HOSIDIUS_TITHE_C_2_DRY; } public boolean isStage2() { - return getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_A_3_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_B_3_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_C_3_DRY; + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int id = obj.getId(); + return id == ObjectID.HOSIDIUS_TITHE_A_3_DRY + || id == ObjectID.HOSIDIUS_TITHE_B_3_DRY + || id == ObjectID.HOSIDIUS_TITHE_C_3_DRY; } public boolean isWatered() { - var id = getGameObject().getId(); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + var id = obj.getId(); switch (id) { case ObjectID.HOSIDIUS_TITHE_B_1_WET: case ObjectID.HOSIDIUS_TITHE_B_2_WET: diff --git a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java index f8072e5798..b2aaa02d81 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java @@ -39,7 +39,7 @@ @Slf4j public class TormentedDemonPlugin extends Plugin { - public static final String version = "1.2.2"; + public static final String version = "1.2.3"; private static final int CHANGE_ATTACK_STYLE_ANIMATION = 11387; private static final int MELEE_ATTACK_ANIMATION = 11392; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java index 7566910a17..766080f486 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java @@ -2,6 +2,7 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.HeadIcon; +import net.runelite.api.NPC; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -11,12 +12,10 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.JewelleryLocationEnum; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -109,7 +108,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_FIRST_STAIRS: Microbot.status = "Climbing first stairs..."; - if (Rs2GameObject.interact(53623, "Climb-up")) { + if (Microbot.getRs2TileObjectCache().query().interact(53623, "Climb-up")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.CLIMB_SECOND_STAIRS; @@ -118,7 +117,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_SECOND_STAIRS: Microbot.status = "Climbing second stairs..."; - if (Rs2GameObject.interact(53624, "Climb-up")) { + if (Microbot.getRs2TileObjectCache().query().interact(53624, "Climb-up")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.CLIMB_THROUGH; @@ -127,7 +126,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_THROUGH: Microbot.status = "Climbing through the path..."; - if (Rs2GameObject.interact(54082, "Climb-through")) { + if (Microbot.getRs2TileObjectCache().query().interact(54082, "Climb-through")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.LOCATION_THREE; @@ -157,7 +156,7 @@ private void handleBanking(TormentedDemonConfig config) { int maxPrayer = Microbot.getClient().getRealSkillLevel(Skill.PRAYER); if (currentHealth < maxHealth || currentPrayer < maxPrayer) { - if (Rs2GameObject.interact(FEROX_POOL_ID, "Drink")) { + if (Microbot.getRs2TileObjectCache().query().interact(FEROX_POOL_ID, "Drink")) { Rs2Player.waitForAnimation(); sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) == maxHealth && @@ -250,16 +249,17 @@ private void handleFighting(TormentedDemonConfig config) { Rs2Player.eatAt(config.minEatPercent()); Rs2Player.drinkPrayerPotionAt(config.minPrayerPercent()); - var interactingTarget = (Rs2NpcModel) Rs2Player.getInteracting(); + var interactingActor = Rs2Player.getInteracting(); + int interactingIndex = (interactingActor instanceof NPC) ? ((NPC) interactingActor).getIndex() : -1; if (currentTarget == null) return; - if (interactingTarget == null || interactingTarget.getIndex() != currentTarget.getIndex()) { - boolean attackSuccessful = Rs2Npc.interact(currentTarget, "attack"); + if (interactingActor == null || interactingIndex != currentTarget.getIndex()) { + boolean attackSuccessful = currentTarget.click("attack"); if (attackSuccessful) { Rs2Player.waitForAnimation(); - sleepUntil(() -> Rs2Player.getInteracting() != null && ((Rs2NpcModel) Rs2Player.getInteracting()).getIndex() == currentTarget.getIndex(), 3000); + sleepUntil(() -> Rs2Player.getInteracting() instanceof NPC && ((NPC) Rs2Player.getInteracting()).getIndex() == currentTarget.getIndex(), 3000); } else { logOnceToChat("Attack failed for target: " + (currentTarget != null ? currentTarget.getName() : "null")); currentTarget = null; @@ -320,9 +320,11 @@ private void switchOffensivePrayer(Rs2PrayerEnum newOffensivePrayer) { } private Rs2NpcModel findNewTarget(TormentedDemonConfig config) { - return Rs2Npc.getAttackableNpcs("Tormented Demon") - .filter(npc -> npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> { + return Microbot.getRs2NpcCache().query() + .withName("Tormented Demon") + .where(npc -> !npc.isDead()) + .where(npc -> npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> { HeadIcon demonHeadIcon = npc.getHeadIcon(); if (demonHeadIcon != null) { switchGear(config, demonHeadIcon); @@ -331,8 +333,7 @@ private Rs2NpcModel findNewTarget(TormentedDemonConfig config) { logOnceToChat("Null HeadIcon for NPC " + npc.getName()); return false; }) - .findFirst() - .orElse(null); + .firstOnClientThread(); } private void switchGear(TormentedDemonConfig config, HeadIcon combatNpcHeadIcon) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java index c7e6663c61..f0bf31a13d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.toweroflife_creaturecreation; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.VarbitID; @@ -14,15 +13,15 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -209,11 +208,11 @@ void HandleStateMachine(TowerOfLifeCCConfig _config) { if (!inBasement && !Rs2Player.isMoving()) { - TileObject trapdoor = Rs2GameObject.getTileObject(new WorldPoint(2648, 3212, 0)); + Rs2TileObjectModel trapdoor = Microbot.getRs2TileObjectCache().query().within(new WorldPoint(2648, 3212, 0), 1).nearest(); if (trapdoor != null) { Microbot.log("Trapdoor id: " + trapdoor.getId()); - Rs2GameObject.interact(trapdoor, "Climb-down"); + trapdoor.click("Climb-down"); } else { @@ -405,11 +404,11 @@ void HandleCreature(LootingParameters params, int disposableItem, int secondaryI //Rs2Inventory.waitForInventoryChanges(3000); //Rs2Inventory.useItemOnObject(secondaryItem, altarObjectId); //Rs2Inventory.waitForInventoryChanges(3000); - Rs2GameObject.interact(altarObjectId, "Activate"); - sleepUntil(() -> { summonedCreature = Rs2Npc.getNpcs() - .filter(npc -> npc != null - && (npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer())) - .findFirst().orElse(null); + Microbot.getRs2TileObjectCache().query().interact(altarObjectId, "Activate"); + sleepUntil(() -> { summonedCreature = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null + && (npc.getNpc().getInteracting() == null || npc.getNpc().getInteracting() == Microbot.getClient().getLocalPlayer())) + .nearest(); return summonedCreature != null; }, 5000); //Microbot.log("Summoned creature"); @@ -427,9 +426,9 @@ void HandleCreature(LootingParameters params, int disposableItem, int secondaryI } else if (!Rs2Combat.inCombat()) { - if (summonedCreature != null && !summonedCreature.isDead()) + if (summonedCreature != null && !summonedCreature.getNpc().isDead()) { - Rs2Npc.attack(summonedCreature); + summonedCreature.click("Attack"); } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java index 312131c59b..d99aac9841 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.tutorialisland; import com.google.inject.Provides; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -26,20 +24,20 @@ enabledByDefault = PluginConstants.DEFAULT_ENABLED, isExternal = PluginConstants.IS_EXTERNAL ) -@Slf4j public class TutorialIslandPlugin extends Plugin { - public static final String version = "1.3.1"; + public static final String version = "1.3.16"; - @Getter private boolean toggleMusic; - @Getter private boolean toggleRoofs; - @Getter private boolean toggleLevelUp; - @Getter private boolean toggleShiftDrop; - @Getter private boolean toggleDevOverlay; + + public boolean isToggleMusic() { return toggleMusic; } + public boolean isToggleRoofs() { return toggleRoofs; } + public boolean isToggleLevelUp() { return toggleLevelUp; } + public boolean isToggleShiftDrop() { return toggleShiftDrop; } + public boolean isToggleDevOverlay() { return toggleDevOverlay; } @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java index 38caed22d1..a8e567a28d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java @@ -12,14 +12,12 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.NameGenerator; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; @@ -44,11 +42,16 @@ public class TutorialIslandScript extends Script { public static Status status = Status.NAME; final int CharacterCreation = 679; final int[] CharacterCreation_Arrows = new int[]{13, 17, 21, 25, 29, 33, 37, 44, 48, 52, 56, 60}; + private static final int MIN_RANDOMIZATION_ROUNDS = 8; private final TutorialIslandPlugin plugin; private final int NameCreation = 558; private boolean toggledSettings = false; private boolean toggledMusic = false; + private boolean triedRoofs = false; + private boolean triedShiftDrop = false; + private boolean triedLevelUp = false; private boolean hasSelectedGender = false; + private int randomizationRounds = 0; @Inject public TutorialIslandScript(TutorialIslandPlugin plugin) { @@ -68,6 +71,18 @@ public boolean run(TutorialIslandConfig config) { CalculateStatus(); + if (Rs2Widget.isWidgetVisible(929, 5)) { + Rs2Widget.clickWidget(929, 5); + Rs2Random.waitEx(1200, 300); + return; + } + + if (Rs2Widget.isWidgetVisible(310, 0)) { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + Rs2Random.waitEx(1200, 300); + return; + } + if (hasContinue()) { clickContinue(); return; @@ -156,7 +171,7 @@ public boolean run(TutorialIslandConfig config) { break; } } catch (Exception ex) { - System.out.println(ex.getMessage()); + ex.printStackTrace(); } }, 0, 600, TimeUnit.MILLISECONDS); return true; @@ -205,54 +220,56 @@ public void CalculateStatus() { } public void RandomizeCharacter() { - if (Rs2Random.diceFractional(0.2)) { - selectGender(); - - if (Rs2Random.diceFractional(0.25)) { // chance to change pronouns - System.out.println("changing pronouns..."); - Widget pronounWidget = Rs2Widget.getWidget(CharacterCreation, 72); // open pronouns DropDown - Widget currentPronoun = Arrays.stream(pronounWidget.getDynamicChildren()).filter(pnw -> pnw.getText().toLowerCase().contains("he/him") || pnw.getText().toLowerCase().contains("they/them") || pnw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - Rs2Widget.clickWidget(pronounWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - Widget[] dynamicPronounWidgets = Rs2Widget.getWidget(CharacterCreation, 78).getDynamicChildren(); - Widget pronounSelectionWidget; - - if (currentPronoun != null) { - if (currentPronoun.getText().toLowerCase().contains("he/him")) { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - } - } else { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("he/him")).findFirst().orElse(null); - } - } - - Rs2Widget.clickWidget(pronounSelectionWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - } - } - - Rs2Widget.clickWidget(CharacterCreation, 74); // confirm Button - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !isCharacterCreationVisible()); - } - int randomIndex = (int) Math.floor(Math.random() * CharacterCreation_Arrows.length); int item = CharacterCreation_Arrows[randomIndex]; item += Math.random() < 0.5 ? 2 : 3; // Select Up / Down Arrow for random index Widget widget = Rs2Widget.getWidget(CharacterCreation, item); + if (widget == null) return; for (int i = 0; i < Rs2Random.between(1, 6); i++) { Rs2Widget.clickWidget(widget.getId()); Rs2Random.waitEx(300, 50); } + randomizationRounds++; + + if (randomizationRounds < MIN_RANDOMIZATION_ROUNDS || !Rs2Random.diceFractional(0.2)) return; + + selectGender(); + + if (Rs2Random.diceFractional(0.25)) { // chance to change pronouns + System.out.println("changing pronouns..."); + Widget pronounWidget = Rs2Widget.getWidget(CharacterCreation, 72); // open pronouns DropDown + Widget currentPronoun = Arrays.stream(pronounWidget.getDynamicChildren()).filter(pnw -> pnw.getText().toLowerCase().contains("he/him") || pnw.getText().toLowerCase().contains("they/them") || pnw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); + Rs2Widget.clickWidget(pronounWidget); + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options + Widget[] dynamicPronounWidgets = Rs2Widget.getWidget(CharacterCreation, 78).getDynamicChildren(); + Widget pronounSelectionWidget; + + if (currentPronoun != null) { + if (currentPronoun.getText().toLowerCase().contains("he/him")) { + if (Rs2Random.diceFractional(0.5)) { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); + } else { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); + } + } else { + if (Rs2Random.diceFractional(0.5)) { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); + } else { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("he/him")).findFirst().orElse(null); + } + } + + Rs2Widget.clickWidget(pronounSelectionWidget); + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> !Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options + } + } + + Rs2Widget.clickWidget(CharacterCreation, 74); // confirm Button + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> !isCharacterCreationVisible()); } /** @@ -291,8 +308,52 @@ private void selectGender() { hasSelectedGender = true; } + private boolean walkAndTalk(Rs2NpcModel npc) { + return walkAndTalk(npc, 2); + } + + private boolean walkAndTalk(Rs2NpcModel npc, int reach) { + return walkAndAct(npc, reach, "Talk-to", () -> sleepUntil(Rs2Dialogue::isInDialogue, 5000)); + } + + private boolean walkAndAct(Rs2NpcModel npc, int reach, String action, Runnable afterClick) { + if (npc == null) return false; + WorldPoint npcLoc = npc.getWorldLocation(); + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (npcLoc == null || playerLoc == null) return false; + if (playerLoc.distanceTo(npcLoc) > reach) { + Rs2Walker.walkTo(npcLoc, reach); + Rs2Player.waitForWalking(); + return false; + } + if (npc.click(action)) { + if (afterClick != null) afterClick.run(); + return true; + } + return false; + } + + private boolean walkAndAttackRat() { + // The rat pit gate (id 9719) blocks both Rs2Walker and native pathfinder. + // Explicitly open it if we're standing adjacent; after passing through, attack directly. + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.getX() == 3111 && playerLoc.getY() >= 9516 && playerLoc.getY() <= 9519) { + if (Microbot.getRs2TileObjectCache().query().withId(9719).interact("Open")) { + sleepUntil(() -> { + WorldPoint p = Rs2Player.getWorldLocation(); + return p != null && p.getY() < 9516; + }, 3000); + return false; + } + } + Rs2NpcModel rat = Microbot.getRs2NpcCache().query().withName("Giant rat").nearest(); + if (rat == null || rat.getWorldLocation() == null) return false; + if (!Rs2Walker.canReach(rat.getWorldLocation())) return false; + return rat.click("Attack"); + } + public void GettingStarted() { - var npc = Rs2Npc.getNpc(NpcID.GIELINOR_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.GIELINOR_GUIDE).nearest(); if (hasContinue()) return; @@ -302,9 +363,7 @@ public void GettingStarted() { return; } - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) < 8) { if (!toggledSettings) { @@ -315,26 +374,29 @@ public void GettingStarted() { } if (plugin.isToggleMusic() && !toggledMusic) { - turnOffMusic(); toggledMusic = true; + try { turnOffMusic(); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleRoofs() && !isHideRoofsEnabled()) { - hideRoofs(false); + if (plugin.isToggleRoofs() && !triedRoofs && !isHideRoofsEnabled()) { + triedRoofs = true; + try { hideRoofs(false); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleShiftDrop() && !isDropShiftSettingEnabled()) { - enableDropShiftSetting(false); + if (plugin.isToggleShiftDrop() && !triedShiftDrop && !isDropShiftSettingEnabled()) { + triedShiftDrop = true; + try { enableDropShiftSetting(false); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleLevelUp() && isLevelUpNotificationsEnabled()) { - disableLevelUpNotifications(true); + if (plugin.isToggleLevelUp() && !triedLevelUp && isLevelUpNotificationsEnabled()) { + triedLevelUp = true; + try { disableLevelUpNotifications(true); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } @@ -345,28 +407,18 @@ public void GettingStarted() { sleepUntil(() -> Rs2Camera.getPitch() > 250); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } public void SurvivalGuide() { - var npc = Rs2Npc.getNpc(NpcID.SURVIVAL_EXPERT); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.SURVIVAL_EXPERT).nearest(); if (Microbot.getVarbitPlayerValue(281) == 10 || Microbot.getVarbitPlayerValue(281) == 20 || Microbot.getVarbitPlayerValue(281) == 60) { - if (!Rs2Npc.hasLineOfSight(npc)) { - Rs2Walker.walkTo(npc.getWorldLocation(), 4); - Rs2Player.waitForWalking(); - } - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) < 40) { Rs2Random.waitEx(1200, 300); var widget = Rs2Widget.findWidget("Inventory", true); @@ -378,25 +430,21 @@ public void SurvivalGuide() { var widget = Rs2Widget.findWidget("Skills", true); Rs2Widget.clickWidget(widget); // switchToSkillsTab Rs2Random.waitEx(1200, 300); - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) <= 90) { if (!Rs2Inventory.hasItem("Bronze Axe") || !Rs2Inventory.hasItem("Tinderbox")) { - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); return; } - if (!Rs2Inventory.contains("Raw shrimps")) { + if (!Rs2Inventory.contains(false, "shrimps")) { fishShrimp(); return; } - if (!Rs2Inventory.contains("Logs") && (!Rs2GameObject.exists(ObjectID.FIRE_26185) || Rs2Player.getRealSkillLevel(Skill.WOODCUTTING) == 0)) { + if (!Rs2Inventory.contains("Logs") && (Microbot.getRs2TileObjectCache().query().withId(ObjectID.FIRE_26185).nearest() == null || Rs2Player.getRealSkillLevel(Skill.WOODCUTTING) == 0)) { CutTree(); return; } - if (!Rs2GameObject.exists(ObjectID.FIRE_26185)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.FIRE_26185).nearest() == null) { LightFire(); return; } @@ -405,7 +453,7 @@ public void SurvivalGuide() { } public void MageGuide() { - var npc = Rs2Npc.getNpc(NpcID.MAGIC_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MAGIC_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) == 610 || Microbot.getVarbitPlayerValue(281) == 620) { WorldPoint worldPoint = new WorldPoint(3141, 3088, 0); @@ -415,84 +463,82 @@ public void MageGuide() { if (distance > 8) { Rs2Walker.walkTo(targetPoint, 8); } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } else if (Microbot.getVarbitPlayerValue(281) == 630) { var widget = Rs2Widget.findWidget("Magic", true); Rs2Widget.clickWidget(widget); // switchToMagicTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 640) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 650) { widgetCast(); - } else if (Microbot.getVarbitPlayerValue(281) == 670) { - Rs2Dialogue.clickContinue(); + } else if (Microbot.getVarbitPlayerValue(281) == 680) { + if (Rs2Tab.getCurrentTab() != InterfaceTab.MAGIC) { + Rs2Tab.switchTo(InterfaceTab.MAGIC); + Rs2Random.waitEx(600, 100); + } + Widget homeTeleport = Rs2Widget.findWidget("Lumbridge Home Teleport", true); + if (homeTeleport != null) { + Rs2Widget.clickWidget(homeTeleport); + sleepUntil(() -> { + WorldPoint p = Rs2Player.getWorldLocation(); + return p != null && p.getX() >= 3200; + }, 15_000); + } + } else if (Microbot.getVarbitPlayerValue(281) >= 660) { if (isInDialogue()) { - if (Rs2Widget.hasWidget("Do you want to go to the mainland?")) { - Rs2Keyboard.typeString("1"); - return; - } if (hasSelectAnOption()) { - Widget widgetOptions = Rs2Widget.getWidget(219, 1); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (int i = 0; i < dynamicWidgetOptions.length; i++) { - String optionText = dynamicWidgetOptions[i].getText(); - - if (optionText.contains("Yes, send me to the mainland") || optionText.contains("No, I'm not planning to do that")) { - Rs2Keyboard.typeString(String.valueOf(i)); - break; - } - } - } - } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); + if (Rs2Dialogue.keyPressForDialogueOption("Yes, I'd like to go to the mainland")) return; + if (Rs2Dialogue.keyPressForDialogueOption("Yes, send me to the mainland")) return; + if (Rs2Dialogue.keyPressForDialogueOption("Yes")) return; + Rs2Dialogue.keyPressForDialogueOption(1); + return; } + Rs2Dialogue.clickContinue(); + return; } + + walkAndTalk(npc); } } public void PrayerGuide() { - var npc = Rs2Npc.getNpc(NpcID.BROTHER_BRACE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.BROTHER_BRACE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 640 || Microbot.getVarbitPlayerValue(281) == 550 || Microbot.getVarbitPlayerValue(281) == 540) { Rs2Walker.walkTo(new WorldPoint(3124, 3106, 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 560) { var widget = Rs2Widget.findWidget("Prayer", true); Rs2Widget.clickWidget(widget); // switchToPrayerTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 570) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 580) { var widget = Rs2Widget.findWidget("Friends list", true); Rs2Widget.clickWidget(widget); // switchToFriendsTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 600) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } public void BankerGuide() { - var npc = Rs2Npc.getNpc(NpcID.ACCOUNT_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.ACCOUNT_GUIDE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 510) { - Rs2GameObject.interact(ObjectID.BANK_BOOTH_10083); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BANK_BOOTH_10083); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 510); } else if (Microbot.getVarbitPlayerValue(281) == 520) { + if (Rs2Widget.isWidgetVisible(928, 4)) { + Rs2Widget.clickWidget(928, 4); // Close poll booth interface + Rs2Random.waitEx(1200, 300); + return; + } + if (Rs2Widget.isWidgetVisible(289, 5)) { Widget widgetOptions = Rs2Widget.getWidget(289, 4); Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); @@ -512,9 +558,15 @@ public void BankerGuide() { Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - Rs2GameObject.interact(26815); //interactWithPollBooth - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520); + Microbot.getRs2TileObjectCache().query().interact(26815); //interactWithPollBooth + sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520 || Rs2Widget.isWidgetVisible(928, 4)); } else if (Microbot.getVarbitPlayerValue(281) == 525 || Microbot.getVarbitPlayerValue(281) == 530) { + if (Rs2Widget.isWidgetVisible(928, 4)) { + Rs2Widget.clickWidget(928, 4); // Close poll booth interface + Rs2Random.waitEx(1200, 300); + return; + } + if (Rs2Widget.isWidgetVisible(310, 2)) { Widget widgetOptions = Rs2Widget.getWidget(310, 2); Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); @@ -534,9 +586,7 @@ public void BankerGuide() { Rs2Walker.walkTo(npc.getWorldLocation(), 3); Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 531) { var widget = Rs2Widget.findWidget("Account Management", true); Rs2Widget.clickWidget(widget); // switchToAccountManagementTab @@ -546,21 +596,17 @@ public void BankerGuide() { clickContinue(); return; } - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } public void CombatGuide() { - var npc = Rs2Npc.getNpc(NpcID.COMBAT_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.COMBAT_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) <= 370) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3106, 3108), Rs2Random.between(9508, 9510), 0)); Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) <= 410) { if (isInDialogue()) { clickContinue(); @@ -592,68 +638,65 @@ public void CombatGuide() { } } - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 500) { Rs2Walker.walkTo(new WorldPoint(3111, 9526, Rs2Player.getWorldLocation().getPlane())); Rs2Player.waitForWalking(); - Rs2GameObject.interact("Ladder", "Climb-up"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Ladder").interact("Climb-up")); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 500); } else if (Microbot.getVarbitPlayerValue(281) == 480 || Microbot.getVarbitPlayerValue(281) == 490) { Actor rat = Rs2Player.getInteracting(); if (rat != null && rat.getName().equalsIgnoreCase("giant rat")) return; - Rs2Inventory.wield("Shortbow"); - Rs2Random.waitEx(600, 100); - Rs2Inventory.wield("Bronze arrow"); - Rs2Random.waitEx(600, 100); - if (Rs2Random.between(1, 5) == 2) { - Rs2Walker.walkTo(new WorldPoint(3110, 9523, 0), 4); + if (Rs2Inventory.hasItem("Shortbow")) { + Rs2Inventory.wield("Shortbow"); + Rs2Random.waitEx(600, 100); } - Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); - } else if (Microbot.getVarbitPlayerValue(281) == 470) { - Rs2Walker.walkTo(npc.getWorldLocation()); - Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); + if (Rs2Inventory.hasItem("Bronze arrow")) { + Rs2Inventory.wield("Bronze arrow"); + Rs2Random.waitEx(600, 100); } + walkAndAttackRat(); + } else if (Microbot.getVarbitPlayerValue(281) == 470) { + if (npc == null) return; + walkAndTalk(npc); + } else if (Microbot.getVarbitPlayerValue(281) == 430) { + var widget = Rs2Widget.findWidget("Combat Options", true); + Rs2Widget.clickWidget(widget); + Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) >= 420) { + if (isInDialogue()) { + clickContinue(); + return; + } if (Microbot.getClient().getLocalPlayer().isInteracting() || Rs2Player.isAnimating()) return; if (Rs2Equipment.isWearing("Bronze sword")) { - var widget = Rs2Widget.findWidget("Combat Options", true); - Rs2Widget.clickWidget(widget); // switchToQuestTab - Rs2Random.waitEx(1200, 300); - WorldPoint worldPoint = new WorldPoint(3105, 9517, 0); - Rs2Walker.walkTo(worldPoint, 3); - Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); - } else { + walkAndAttackRat(); + } else if (Rs2Inventory.hasItem("Bronze sword")) { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); Rs2Inventory.wield("Bronze sword"); Rs2Random.waitEx(600, 100); Rs2Inventory.wield("Wooden shield"); + } else { + walkAndTalk(npc); } } } public void MiningGuide() { - var npc = Rs2Npc.getNpc(NpcID.MINING_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MINING_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) == 260) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3082, 3085), Rs2Random.between(9502, 9505), 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else { if (Rs2Inventory.contains("Bronze dagger")) { - Rs2GameObject.interact(ObjectID.GATE_9718, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GATE_9718, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) > 360); return; } if (Rs2Inventory.contains("Bronze bar") && Rs2Inventory.contains("Hammer")) { - Rs2GameObject.interact("Anvil", "Smith"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Anvil").interact("Smith")); sleepUntil(Rs2Widget::isSmithingWidgetOpen); Rs2Widget.clickWidget(312, 9); // Smith Bronze Dagger Rs2Random.waitEx(1200, 300); @@ -661,9 +704,7 @@ public void MiningGuide() { return; } if (Rs2Inventory.contains("Bronze bar") && !Rs2Inventory.contains("Hammer")) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); return; } if (Rs2Inventory.contains("Bronze pickaxe") && (!Rs2Inventory.contains("Copper ore") || !Rs2Inventory.contains("Tin ore"))) { @@ -678,7 +719,7 @@ public void MiningGuide() { Collections.shuffle(rockIds); int rockId = rockIds.get(0); - Rs2GameObject.interact(rockId, "Mine"); + Microbot.getRs2TileObjectCache().query().interact(rockId, "Mine"); sleepUntil(() -> { if (rockId == ObjectID.COPPER_ROCKS) { return Rs2Inventory.contains("Copper ore") && !Rs2Player.isAnimating(1800); @@ -696,15 +737,14 @@ public void MiningGuide() { } public void QuestGuide() { - var npc = Rs2Npc.getNpc(NpcID.QUEST_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.QUEST_GUIDE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 200 || Microbot.getVarbitPlayerValue(281) == 210) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3083, 3086), Rs2Random.between(3127, 3129), 0)); - Rs2GameObject.interact(9716, "Open"); + Microbot.getRs2TileObjectCache().query().interact(9716, "Open"); Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 220 || Microbot.getVarbitPlayerValue(281) == 240) { - Rs2Npc.interact(npc, "Talk-to"); - sleepUntil(Rs2Dialogue::isInDialogue); + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 230) { var widget = Rs2Widget.findWidget("Quest List", true); Rs2Widget.clickWidget(widget); // switchToQuestTab @@ -712,36 +752,34 @@ public void QuestGuide() { } else { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); - Rs2GameObject.interact(9726, "Climb-down"); + Microbot.getRs2TileObjectCache().query().interact(9726, "Climb-down"); Rs2Random.waitEx(2400, 100); } } public void CookingGuide() { - var npc = Rs2Npc.getNpc(NpcID.MASTER_CHEF); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MASTER_CHEF).nearest(); if (Microbot.getVarbitPlayerValue(281) == 120) { Rs2Random.waitEx(1200, 300); Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - Rs2GameObject.interact(ObjectID.GATE_9470, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GATE_9470, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 120); } else if (Microbot.getVarbitPlayerValue(281) == 130) { - Rs2GameObject.interact(ObjectID.DOOR_9709, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.DOOR_9709, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 130); } else if (Microbot.getVarbitPlayerValue(281) == 140) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) >= 150 && Microbot.getVarbitPlayerValue(281) < 200) { if (!Rs2Inventory.contains("Bread dough") && !Rs2Inventory.contains("Bread")) { Rs2Inventory.combine("Bucket of water", "Pot of flour"); sleepUntil(() -> Rs2Inventory.contains("Dough"), 2000); } else if (Rs2Inventory.contains("Bread dough")) { Rs2Inventory.interact("Bread dough"); - Rs2GameObject.interact(9736, "Use"); + Microbot.getRs2TileObjectCache().query().interact(9736, "Use"); sleepUntil(() -> Rs2Inventory.contains("Bread")); } else if (Rs2Inventory.contains("Bread")) { - if (Rs2GameObject.interact(9710, "Open")) { + if (Microbot.getRs2TileObjectCache().query().interact(9710, "Open")) { Rs2Random.waitEx(2400, 100); } } @@ -759,20 +797,20 @@ public void LightFire() { } public void CutTree() { - Rs2GameObject.interact("Tree", "Chop down"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Tree").interact("Chop down")); sleepUntil(() -> Rs2Inventory.hasItem("Logs") && !Rs2Player.isAnimating(2400)); } public void fishShrimp() { - Rs2Npc.interact(NpcID.FISHING_SPOT_3317, "Net"); - sleepUntil(() -> Rs2Inventory.contains("Raw shrimps")); + Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_3317).interact("Net"); + sleepUntil(() -> Rs2Inventory.contains(false, "shrimps")); } private boolean widgetCast() { if (Rs2Player.isAnimating() || Rs2Player.getInteracting() != null) return true; - Widget windStrike = Rs2Widget.getWidget(218, 8); - if (windStrike == null) windStrike = Rs2Widget.findWidget("Wind Strike", null, true); + Widget windStrike = Rs2Widget.findWidget("Wind Strike", null, true); + if (windStrike == null) windStrike = Rs2Widget.getWidget(218, 11); if (windStrike == null) return false; boolean hidden; @@ -801,11 +839,11 @@ private boolean widgetCast() { Rs2Widget.clickWidget(windStrike); Rs2Random.waitEx(150, 50); - Rs2NpcModel chicken = Rs2Npc.getNpcs("chicken").findFirst().orElse(null); + Rs2NpcModel chicken = Microbot.getRs2NpcCache().query().withName("chicken").nearestOnClientThread(); if (chicken == null) return false; - if (!Rs2Npc.interact(chicken, "Cast")) { - Rs2Npc.interact(chicken); + if (!chicken.click("Cast")) { + chicken.click("Cast"); } sleepUntil(() -> Rs2Player.isAnimating() || Microbot.getVarbitPlayerValue(281) != 650, 2_000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index 4a795bed6a..1848816778 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.6"; + static final String version = "1.0.10"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java index 219f3b0b46..b7ac2f0f61 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java @@ -22,6 +22,7 @@ public class ValeTotemScript extends Script { private ValeTotemConfig config; private GameSession gameSession; private boolean isRunning = false; + private long lastStateLogTime = 0; public boolean run(ValeTotemConfig config) { this.config = config; @@ -71,7 +72,12 @@ public boolean run(ValeTotemConfig config) { */ private void executeMainLoop() { GameState currentState = gameSession.getCurrentState(); - + long now = System.currentTimeMillis(); + if (now - lastStateLogTime > 5000) { + lastStateLogTime = now; + Microbot.log("[ValeTotem] State: " + currentState.name() + " | Player: " + net.runelite.client.plugins.microbot.valetotems.utils.CoordinateUtils.getPlayerLocation()); + } + switch (currentState) { case IDLE: handleIdleState(); @@ -127,8 +133,9 @@ private void handleIdleState() { * Handle banking operations */ private void handleBankingState() { - // Use unified banking method that detects route type and updates game session + Microbot.log("[ValeTotem] handleBankingState: starting unified banking cycle"); boolean bankingSuccess = BankingHandler.performUnifiedBankingCycle(gameSession); + Microbot.log("[ValeTotem] handleBankingState: result=" + bankingSuccess); if (bankingSuccess) { gameSession.startNewRound(); @@ -139,7 +146,7 @@ private void handleBankingState() { gameSession.setCurrentTotem(TotemLocation.getFirst(currentRouteType)); // Calculate reward collection frequency - RewardHandler.COLLECTION_FREQUENCY = config.collectOfferingsFrequency() - 2 + RandomUtils.nextInt(1, 4); + RewardHandler.COLLECTION_FREQUENCY = Math.max(1, config.collectOfferingsFrequency() - 2 + RandomUtils.nextInt(1, 4)); Microbot.log("Reward collection frequency set to: " + RewardHandler.COLLECTION_FREQUENCY); // Log the route being used @@ -304,9 +311,8 @@ private void handleReturningToBankState() { * Handle error states */ private void handleErrorState() { - System.err.println("Bot in error state. Attempting recovery..."); - - // Try emergency procedures + Microbot.log("[ValeTotem] ERROR state. Errors so far: " + gameSession.getErrorMessages().size() + ". Attempting recovery..."); + FletchingHandler.emergencyStopFletching(); NavigationHandler.emergencyReturnToBank(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java index dce7fff271..4bf71d13a3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java @@ -15,8 +15,8 @@ public enum GameObjectId { TOTEM_READY_FOR_DECORATION(57081, "totem", "Totem ready for decoration - Action: Decorate"), // Ent trails (optional for extra points) - ENT_TRAIL_1(57116, "ent", "Ent trail for bonus points"), - ENT_TRAIL_2(57115, "ent", "Ent trail for bonus points"), + ENT_TRAIL_1(57116, "ent trail", "Ent trail for bonus points"), + ENT_TRAIL_2(57115, "ent trail", "Ent trail for bonus points"), // Offerings (rewards) - different states based on reward count OFFERINGS_MANY(57098, "offering", "Offerings pile with many rewards - Action: Claim"), @@ -65,11 +65,17 @@ public static GameObjectId getById(int id) { * @return true if it's any type of offerings pile */ public static boolean isOfferingsPile(int id) { - return id == OFFERINGS_MANY.getId() || - id == OFFERINGS_SOME.getId() || + return id == OFFERINGS_MANY.getId() || + id == OFFERINGS_SOME.getId() || id == OFFERINGS_EMPTY.getId(); } + public static boolean isTotemObject(int id) { + return id == TOTEM_SITE.getId() || + id == EMPTY_TOTEM.getId() || + id == TOTEM_READY_FOR_DECORATION.getId(); + } + /** * Check if the offerings pile has claimable rewards * @param id the object ID to check diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java index afa1cd6c05..6b97da0f2a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java @@ -21,6 +21,7 @@ import static net.runelite.client.plugins.microbot.util.Global.sleep; import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; import java.util.Random; import net.runelite.client.plugins.microbot.valetotems.enums.TotemLocation; @@ -57,26 +58,28 @@ public static void setConfig(ValeTotemConfig config) { */ public static boolean navigateToBank() { try { - // Check if already near bank - if (CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { - // Move to optimal banking position if not there + boolean alreadyNear = CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE); + Microbot.log("[Banking] navigateToBank: alreadyNear=" + alreadyNear + " pos=" + CoordinateUtils.getPlayerLocation()); + + if (alreadyNear) { if (!CoordinateUtils.isAtBankingPosition()) { + Microbot.log("[Banking] Walking to optimal banking tile"); return Rs2Walker.walkTo(BankLocation.PLAYER_STANDING_TILE.getLocation()); } return true; } - // Walk to bank area + Microbot.log("[Banking] Walking to bank booth at " + BankLocation.BANK_BOOTH.getLocation()); boolean walked = Rs2Walker.walkTo(BankLocation.BANK_BOOTH.getLocation()); + Microbot.log("[Banking] walkTo bank result=" + walked); if (walked) { - // Wait for arrival and position correctly Rs2Player.waitForWalking(); return Rs2Walker.walkTo(BankLocation.PLAYER_STANDING_TILE.getLocation()); } return false; } catch (Exception e) { - Microbot.log("Error navigating to bank: " + e.getMessage()); + Microbot.log("[Banking] Error navigating to bank: " + e.getMessage()); return false; } } @@ -87,37 +90,39 @@ public static boolean navigateToBank() { */ public static boolean openBank() { try { - // Check if bank is already open if (Rs2Bank.isOpen()) { + Microbot.log("[Banking] Bank already open"); return true; } - // Ensure we're close enough to the bank if (!CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { + Microbot.log("[Banking] openBank: not near bank, navigating first"); if (!navigateToBank()) { return false; } } - // Interact with bank booth using string search - boolean interacted = GameObjectUtils.findAndInteractAtLocationByName( - GameObjectId.BANK_BOOTH.getSearchTerm(), + Microbot.log("[Banking] Interacting with bank booth (id=" + GameObjectId.BANK_BOOTH.getId() + " at " + BankLocation.BANK_BOOTH.getLocation() + ")"); + boolean interacted = GameObjectUtils.findAndInteractAtLocation( + GameObjectId.BANK_BOOTH.getId(), BankLocation.BANK_BOOTH.getLocation(), "Bank" ); + Microbot.log("[Banking] Bank booth interact result=" + interacted); if (interacted) { - // Wait for bank to open long startTime = System.currentTimeMillis(); while (!Rs2Bank.isOpen() && System.currentTimeMillis() - startTime < BANK_TIMEOUT_MS) { sleep(100); } - return Rs2Bank.isOpen(); + boolean opened = Rs2Bank.isOpen(); + Microbot.log("[Banking] Bank opened=" + opened); + return opened; } return false; } catch (Exception e) { - Microbot.log("Error opening bank: " + e.getMessage()); + Microbot.log("[Banking] Error opening bank: " + e.getMessage()); return false; } } @@ -172,26 +177,22 @@ public static boolean withdrawRequiredItems(GameSession gameSession) { int logId = InventoryUtils.getLogId(); String logTypeName = config != null ? config.logType().getDisplayName() : "Yew Logs"; - // Withdraw logs (materials check already performed, so this should succeed) + // Rs2Bank.withdrawX only queues the menu click — it returns immediately and the item + // can lag several ticks behind. Generic waitForInventoryChanges + hasRequiredItems + // was racing and reporting shortages right after a successful withdraw. Microbot.log("Withdrawing " + logsToWithdraw + " " + logTypeName + " from bank"); - boolean withdrew = Rs2Bank.withdrawX(logId, logsToWithdraw); - if (withdrew) { - Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); - Rs2Inventory.waitForInventoryChanges(3000); - } else { - Microbot.log("Failed to withdraw logs from bank"); + int logsBefore = InventoryUtils.getLogCount(); + int expectedLogs = logsBefore + logsToWithdraw; + if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { + Microbot.log("Failed to queue log withdrawal"); return false; } - - // Final verification - boolean hasRequired = InventoryUtils.hasRequiredItems(); - if (hasRequired) { - Microbot.log("Successfully completed item withdrawal - all required items now in inventory"); - } else { - Microbot.log("Warning: Required items verification failed after withdrawal"); + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedLogs, 3000)) { + Microbot.log("Log withdrawal timed out (have " + InventoryUtils.getLogCount() + "/" + expectedLogs + ")"); + return false; } - - return hasRequired; + Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); + return InventoryUtils.hasRequiredItems(); } catch (Exception e) { Microbot.log("Error withdrawing required items: " + e.getMessage()); @@ -251,19 +252,26 @@ public static TotemLocation.RouteType detectRouteTypeWithBankOpen() { */ public static boolean performUnifiedBankingCycle(GameSession gameSession) { try { - // Navigate to bank if needed - if (!CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { - if (!navigateToBank()) { - Microbot.log("Failed to navigate to bank"); + var playerPos = CoordinateUtils.getPlayerLocation(); + int distToBank = CoordinateUtils.getDistanceToBank(); + boolean nearBank = CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE); + Microbot.log("[Banking] Start cycle. Player=" + playerPos + " distToBank=" + distToBank + " nearBank=" + nearBank); + + if (!nearBank) { + Microbot.log("[Banking] Not near bank, navigating..."); + boolean navResult = navigateToBank(); + Microbot.log("[Banking] Navigate result=" + navResult + " newPos=" + CoordinateUtils.getPlayerLocation()); + if (!navResult) { return false; } } - // Open bank first + Microbot.log("[Banking] Opening bank..."); if (!openBank()) { - Microbot.log("Failed to open bank"); + Microbot.log("[Banking] Failed to open bank"); return false; } + Microbot.log("[Banking] Bank opened successfully"); // Now detect route type with bank open TotemLocation.RouteType detectedRouteType = detectRouteTypeWithBankOpen(); @@ -592,14 +600,11 @@ public static boolean ensureLogBasketAvailable() { // Check if log basket is in bank and withdraw it if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { Microbot.log("Withdrawing log basket from bank for extended route"); - boolean withdrew = Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID); - if (withdrew) { - Rs2Inventory.waitForInventoryChanges(3000); - return InventoryUtils.hasLogBasket(); - } else { - Microbot.log("Failed to withdraw log basket from bank"); + if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { + Microbot.log("Failed to queue log basket withdrawal"); return false; } + return sleepUntil(InventoryUtils::hasLogBasket, 3000); } Microbot.log("No log basket found in bank"); @@ -656,32 +661,37 @@ private static boolean ensureKnifeInInventory(GameSession gameSession) { return true; } - // Try to withdraw fletching knife first (prioritized) + // Try to withdraw fletching knife first (prioritized). + // Rs2Bank.withdrawOne returns true as soon as the click is queued; we must wait + // on the concrete predicate (hasKnife), not a generic inventory-change wait, + // or a slow server tick causes a stale "no knife" read right after we withdrew — + // the code then used to fall through and try the regular knife, which isn't in + // the bank either, and report a critical shortage on a knife that was in-flight. if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew Fletching knife"); return true; } - } else { - Microbot.log("Failed to withdraw Fletching knife from bank"); + Microbot.log("Fletching knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue Fletching knife withdrawal"); } // Try to withdraw regular knife as fallback if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew regular knife"); return true; } - } else { - Microbot.log("Failed to withdraw regular knife from bank"); + Microbot.log("Regular knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue regular knife withdrawal"); } // No knife found anywhere - critical error @@ -706,42 +716,49 @@ private static boolean ensureKnifeAndLogBasketInInventory(GameSession gameSessio return false; } - // Ensure we have a knife (prioritizing fletching knife) + // Ensure we have a knife (prioritizing fletching knife). Wait on the concrete + // predicate so a slow inventory update doesn't get misread as "no knife in bank". if (!InventoryUtils.hasKnife()) { if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (!Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Microbot.log("Failed to withdraw Fletching knife"); + Microbot.log("Failed to queue Fletching knife withdrawal"); return false; } } else if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (!Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Microbot.log("Failed to withdraw knife"); + Microbot.log("Failed to queue knife withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No knife available (checked inventory and bank for both Fletching knife and regular knife)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasKnife, 3000)) { + Microbot.log("Knife withdrawal timed out"); + return false; + } } // Ensure we have a log basket if (!InventoryUtils.hasLogBasket()) { if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { - Microbot.log("Failed to withdraw log basket"); + Microbot.log("Failed to queue log basket withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No log basket available (checked inventory and bank)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasLogBasket, 3000)) { + Microbot.log("Log basket withdrawal timed out"); + return false; + } } - return InventoryUtils.hasKnife() && InventoryUtils.hasLogBasket(); + return true; } catch (Exception e) { Microbot.log("Error ensuring knife and log basket in inventory: " + e.getMessage()); @@ -821,13 +838,18 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug // Step 1: Take inventory full of logs (leaving space for knife and basket) int logsToWithdraw = InventoryUtils.getOptimalLogBasketLogAmountForExtendedRoute(gameSession) - InventoryUtils.getLogCount(); int logId = InventoryUtils.getLogId(); + int logsBeforeFirst = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { - Microbot.log("Failed to withdraw logs to fill inventory"); + Microbot.log("Failed to queue log withdrawal for basket-fill step"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + int expectedAfterFirst = logsBeforeFirst + logsToWithdraw; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterFirst, 3000)) { + Microbot.log("Log withdrawal timed out in basket-fill step"); + return false; + } // Step 2: Close bank Rs2Bank.closeBank(); @@ -850,11 +872,16 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug int logsStillNeeded = InventoryUtils.getOptimalLogAmountForExtendedRoute(gameSession); if (logsStillNeeded > 0) { Microbot.log("Withdrawing additional " + logsStillNeeded + " logs for extended route"); + int logsBeforeSecond = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsStillNeeded)) { - Microbot.log("Failed to withdraw additional logs"); + Microbot.log("Failed to queue additional log withdrawal"); + return false; + } + int expectedAfterSecond = logsBeforeSecond + logsStillNeeded; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterSecond, 3000)) { + Microbot.log("Additional log withdrawal timed out"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); } Microbot.log("Log basket filling operation completed successfully"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java index a79340173c..4081f24135 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java @@ -66,38 +66,34 @@ public static void setConfig(ValeTotemConfig config) { */ private static int getWidgetHotkey(int childId) { try { - // Calculate the hotkey index based on child ID int hotkeyIndex = getHotkeyIndex(childId); if (hotkeyIndex == -1) { - return -1; // No hotkey mapping + return -1; } - // Check widget text at 270,13[hotkeyIndex] - Widget hotkeyWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, HOTKEY_TEXT_WIDGET_ID); - if (hotkeyWidget != null && hotkeyWidget.getChildren() != null && - hotkeyIndex < hotkeyWidget.getChildren().length) { - - Widget specificHotkeyWidget = hotkeyWidget.getChild(hotkeyIndex); - if (specificHotkeyWidget != null) { - String hotkeyText = specificHotkeyWidget.getText(); - if (hotkeyText != null) { - hotkeyText = hotkeyText.replaceAll("<[^>]*>", "").trim(); // Remove HTML tags - - // Check if it's "Space" - if (hotkeyText.equalsIgnoreCase("Space")) { - return KeyEvent.VK_SPACE; - } - - // Check if it's a number - if (hotkeyText.matches("\\d")) { - int number = Integer.parseInt(hotkeyText); - return number; - } + final int idx = hotkeyIndex; + String hotkeyText = Microbot.getClientThread().invoke(() -> { + Widget hotkeyWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, HOTKEY_TEXT_WIDGET_ID); + if (hotkeyWidget != null && hotkeyWidget.getChildren() != null && + idx < hotkeyWidget.getChildren().length) { + Widget specificHotkeyWidget = hotkeyWidget.getChild(idx); + if (specificHotkeyWidget != null) { + return specificHotkeyWidget.getText(); } } + return null; + }); + + if (hotkeyText != null) { + hotkeyText = hotkeyText.replaceAll("<[^>]*>", "").trim(); + if (hotkeyText.equalsIgnoreCase("Space")) { + return KeyEvent.VK_SPACE; + } + if (hotkeyText.matches("\\d")) { + return Integer.parseInt(hotkeyText); + } } - // Fallback to default number key based on position return getDefaultNumberKey(hotkeyIndex); } catch (Exception e) { @@ -255,19 +251,27 @@ public static boolean selectBow(int quantity) { interactWithWidget(QUANTITY_ALL_CHILD_ID, "all bows"); Microbot.log("Selected all bows"); } else { - // For custom quantities, check if the "Other" option is already set correctly - Widget otherQuantityWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, QUANTITY_OTHER_CHILD_ID); - if (otherQuantityWidget != null) { + String[] widgetInfo = Microbot.getClientThread().invoke(() -> { + Widget otherQuantityWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, QUANTITY_OTHER_CHILD_ID); + if (otherQuantityWidget == null) return null; Widget textWidget = otherQuantityWidget.getChild(9); - if (textWidget != null) { - String currentQuantity = textWidget.getText().replaceAll("<[^>]*>", ""); - if (Integer.parseInt(currentQuantity) == quantity || otherQuantityWidget.getActions() != null) { - if (otherQuantityWidget.getActions() != null) { + String text = textWidget != null ? textWidget.getText() : null; + boolean hasActions = otherQuantityWidget.getActions() != null; + return new String[]{ text, String.valueOf(hasActions) }; + }); + + if (widgetInfo != null) { + String currentText = widgetInfo[0]; + boolean hasActions = Boolean.parseBoolean(widgetInfo[1]); + + if (currentText != null) { + String currentQuantity = currentText.replaceAll("<[^>]*>", ""); + if (Integer.parseInt(currentQuantity) == quantity || hasActions) { + if (hasActions) { interactWithWidget(QUANTITY_OTHER_CHILD_ID, "other quantity"); } Microbot.log("Selected other quantity"); } else { - // If the quantity is not what we want, click "X", type the new quantity, and press enter interactWithWidget(QUANTITY_X_CHILD_ID, "X"); sleepUntil(() -> Rs2Widget.getChildWidgetText(162, 42).contains("Enter amount"), 2000); sleepGaussian(200,100); @@ -277,7 +281,6 @@ public static boolean selectBow(int quantity) { Microbot.log("Selected other quantity1"); } } else { - // If the quantity is null, click "X", type the new quantity, and press enter interactWithWidget(QUANTITY_X_CHILD_ID, "X"); sleepUntil(() -> Rs2Widget.getChildWidgetText(162, 42).contains("Enter amount"), 2000); sleepGaussian(200,100); @@ -292,19 +295,20 @@ public static boolean selectBow(int quantity) { } sleepGaussian(300,100); - - // Click the configured bow option using the mapper - if (config != null) { - int bowChildId = FletchingItemMapper.getFletchingInterfaceChildId(config.logType(), config.bowType()); - String description = FletchingItemMapper.getFletchingDescriptionWithShortcut(config.logType(), config.bowType()); - interactWithWidget(bowChildId, description); - Microbot.log("Selected " + FletchingItemMapper.getFletchingDescription(config.logType(), config.bowType())); - } else { - // Fallback to yew longbow - interactWithWidget(16, "Yew Longbow (u) (expected key: 3)"); - Microbot.log("Selected Yew Longbow (u) - fallback"); + + // Select the configured bow option by name — resolves the correct hotkey dynamically + // against the actual fletching interface layout, instead of assuming fixed child IDs. + // The previous child-ID scheme mapped SHORTBOW → child 15 with hotkey-index 1, but the + // sparse dynamic-children layout of widget 270,13 in skillmulti could produce "3" at + // that slot for some log types, so shortbow-selected was pressing the longbow key. + String bowAction = (config != null && config.bowType() == ValeTotemConfig.BowType.SHORTBOW) + ? "shortbow" : "longbow"; + if (!Rs2Widget.handleProcessingInterface(bowAction)) { + Microbot.log("Failed to select " + bowAction + " from fletching interface"); + return false; } - + Microbot.log("Selected " + bowAction); + sleepGaussian(200,100); return true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java index 8fd3737694..f2e53959c7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.valetotems.handlers; import net.runelite.api.GameObject; +import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.api.Client; @@ -225,11 +226,11 @@ public static boolean navigateToTotem(TotemLocation totemLocation, net.runelite. while (FletchingHandler.isFletchingWhileWalking() && System.currentTimeMillis() - fletchStartTime < 20000) { // 20 sec timeout - // Check for ent trails during fletching - they have absolute priority - List nearbyEntTrails = GameObjectUtils.findGameObjectsByName( - GameObjectId.ENT_TRAIL_1.getSearchTerm(), - CoordinateUtils.getPlayerLocation(), - ENT_TRAIL_SEARCH_RADIUS); + WorldPoint fletchCheckPos = CoordinateUtils.getPlayerLocation(); + List nearbyEntTrails = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_1.getId(), fletchCheckPos, ENT_TRAIL_SEARCH_RADIUS); + nearbyEntTrails.addAll(GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_2.getId(), fletchCheckPos, ENT_TRAIL_SEARCH_RADIUS)); if (!hasWalkedOverEntTrailsThisNavigation && nearbyEntTrails.size() >= 2) { System.out.println("Ent trails detected during fletching - interrupting to prioritize trails"); @@ -257,7 +258,8 @@ public static boolean navigateToTotem(TotemLocation totemLocation, net.runelite. // Priority 4: Regular walking. // Only perform actions if not on cooldown (timer-based, non-blocking) if (!isWalkActionOnCooldown()) { - LocalPoint localNextCheckpoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), nextCheckpoint); + LocalPoint localNextCheckpoint = Microbot.getClientThread().invoke(() -> + LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), nextCheckpoint)); if (localNextCheckpoint != null && Rs2Camera.isTileOnScreen(localNextCheckpoint)) { // Camera turn with cooldown check if (!isCameraTurnOnCooldown()) { @@ -519,17 +521,17 @@ public static boolean checkAndWalkOverEntTrails() { } private static List findAndFilterEntTrails(WorldPoint playerLocation, int searchRadius) { - // Search for ent trails nearby using string search - List entTrails = GameObjectUtils.findGameObjectsByName( - GameObjectId.ENT_TRAIL_1.getSearchTerm(), playerLocation, searchRadius); - - // Filter trails that are within the search radius of the player - entTrails = entTrails.stream() - .filter(trail -> trail.getWorldLocation().distanceTo(playerLocation) <= searchRadius) - .sorted((a, b) -> Integer.compare( - a.getWorldLocation().distanceTo(playerLocation), - b.getWorldLocation().distanceTo(playerLocation))) - .collect(java.util.stream.Collectors.toList()); + List trail1 = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_1.getId(), playerLocation, searchRadius); + List trail2 = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_2.getId(), playerLocation, searchRadius); + + List entTrails = new java.util.ArrayList<>(trail1); + entTrails.addAll(trail2); + + entTrails.sort((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerLocation), + b.getWorldLocation().distanceTo(playerLocation))); return entTrails; } @@ -688,21 +690,19 @@ private static boolean handlePathTransports(List path, int currentIn WorldPoint nextPoint = path.get(currentIndex + 1); // Simple door detection and handling - List doors = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.getAll( + var doors = net.runelite.client.plugins.microbot.Microbot.getRs2TileObjectCache().query().where( obj -> { if (!obj.getWorldLocation().equals(nextPoint)) { return false; } - // Convert TileObject to ObjectComposition to check actions - net.runelite.api.ObjectComposition comp = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.convertToObjectComposition(obj); + net.runelite.api.ObjectComposition comp = obj.getObjectComposition(); return net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Open") || net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Close") || net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Pick-lock"); - } - ); - - for (net.runelite.api.TileObject door : doors) { - if (net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.interact(door)) { + }).toList(); + + for (var door : doors) { + if (door.click()) { sleep(1000); // Wait for door interaction return true; } @@ -756,12 +756,12 @@ private static boolean handlePathTransports(List path, int currentIn } // Now find and interact with the agility shortcut - net.runelite.api.TileObject agilityObj = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.findObjectById(transport.getObjectId()); + var agilityObj = net.runelite.client.plugins.microbot.Microbot.getRs2TileObjectCache().query().withId(transport.getObjectId()).nearest(); if (agilityObj != null && agilityObj.getWorldLocation().distanceTo(transport.getOrigin()) <= 3) { System.out.println("Using agility shortcut: " + transport.getType()); - if (net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.interact(agilityObj, transport.getAction())) { + if (agilityObj != null && agilityObj.click(transport.getAction())) { // Wait until the player has been idle (not walking or animating) for 1.5 seconds System.out.println("Waiting for agility shortcut to complete..."); @@ -775,7 +775,17 @@ private static boolean handlePathTransports(List path, int currentIn sleep(1000); while (System.currentTimeMillis() - startTime < timeout) { - boolean isIdle = !Rs2Player.isMoving() && Microbot.getClient().getLocalPlayer().getAnimation() == -1; + boolean isIdle = false; + if (!Rs2Player.isMoving()) { + int animation = Microbot.getClientThread().invoke(() -> { + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null) { + return Integer.MIN_VALUE; + } + return player.getAnimation(); + }); + isIdle = animation == -1; + } if (isIdle) { if (idleTimeStart == -1) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java index 9bda6112eb..7a2b0774b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java @@ -50,26 +50,26 @@ public static boolean areOfferingsAvailable(TotemLocation totemLocation) { public static boolean collectOfferings(TotemLocation totemLocation, TotemProgress progress, GameSession gameSession) { try { WorldPoint location = totemLocation.getLocation(); - - // Check if already collected for this totem + if (progress.areOfferingsCollected()) { return true; } - if (!shouldCollectOfferings(gameSession)) { + boolean shouldCollect = shouldCollectOfferings(gameSession); + Microbot.log("[Offerings] shouldCollect=" + shouldCollect + " rounds=" + gameSession.getTotalRounds() + " freq=" + COLLECTION_FREQUENCY); + if (!shouldCollect) { return false; } - // Find claimable offerings nearby using string search GameObject offerings = GameObjectUtils.findClaimableOfferings(location, OFFERINGS_SEARCH_RADIUS); - + Microbot.log("[Offerings] findClaimable at " + location + " radius=" + OFFERINGS_SEARCH_RADIUS + " found=" + (offerings != null)); + if (offerings == null) { - System.out.println("No claimable offerings found at " + totemLocation.getDescription()); return false; } - // Check offerings state GameObjectId offeringsState = GameObjectUtils.getOfferingsStateNearLocation(location, OFFERINGS_SEARCH_RADIUS); + Microbot.log("[Offerings] state=" + (offeringsState != null ? offeringsState.name() : "null")); if (offeringsState == null || !GameObjectId.hasClaimableOfferings(offeringsState.getId())) { return false; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java index 4220b9d49c..ebb0f29adc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java @@ -14,10 +14,9 @@ import net.runelite.client.plugins.microbot.valetotems.utils.GameObjectUtils; import net.runelite.client.plugins.microbot.valetotems.utils.InventoryUtils; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -109,7 +108,6 @@ public static boolean buildTotemBase(TotemLocation totemLocation, TotemProgress for (int attempt = 1; attempt <= maxRetries; attempt++) { System.out.println("Building totem base attempt " + attempt + "/" + maxRetries + " at " + totemLocation.getDescription()); - // Look for totem site using string search GameObject totemSite = GameObjectUtils.findObjectAtLocationByName( GameObjectId.TOTEM_SITE.getSearchTerm(), location); @@ -181,7 +179,7 @@ public static boolean identifySpiritAnimals(TotemLocation totemLocation, TotemPr progress.clearIdentifiedAnimals(); // Search for spirit animals in the area - List nearbyNpcs = Rs2Npc.getNpcs() + List nearbyNpcs = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getWorldLocation().distanceTo(location) <= ANIMAL_SEARCH_RADIUS) .filter(npc -> SpiritAnimal.isSpiritAnimal(npc.getId())) .collect(Collectors.toList()); @@ -298,7 +296,8 @@ public static boolean carveAnimalsIntoTotem(TotemLocation totemLocation, TotemPr private static boolean carveAnimalIntoTotem(SpiritAnimal animal) { try { if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { - Rs2GameObject.interact(GameObjectId.EMPTY_TOTEM.getSearchTerm(), "Carve"); + Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(GameObjectId.EMPTY_TOTEM.getSearchTerm()).interact("Carve")); if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { System.err.println("Failed to carve animal"); return false; @@ -347,7 +346,6 @@ public static boolean decorateTotem(TotemLocation totemLocation, TotemProgress p return false; } - // Find the totem object to interact with. GameObject decorationTotem = GameObjectUtils.findObjectAtLocationByName( GameObjectId.TOTEM_READY_FOR_DECORATION.getSearchTerm(), location); @@ -462,7 +460,7 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { sleepGaussian(300,100); // Get the spirit animal's location - WorldPoint animalLocation = Rs2Npc.getNpcs() + WorldPoint animalLocation = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getId() == animal.getNpcId()) .findFirst() .map(npc -> npc.getWorldLocation()) @@ -480,7 +478,7 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { } // Get the spirit animal's NPC model - Rs2NpcModel animalNpc = Rs2Npc.getNpcs() + Rs2NpcModel animalNpc = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getId() == animal.getNpcId()) .findFirst() .orElse(null); @@ -491,14 +489,14 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { } // Check if we have line of sight to the animal - if (Rs2Npc.hasLineOfSight(animalNpc)) { + if (animalNpc.hasLineOfSight()) { System.out.println("Spirit animal has line of sight"); return; } System.out.println("Hovering over spirit animal"); - if (Rs2Npc.hoverOverActor(animalNpc)) { + if (Rs2Npc.hoverOverActor(animalNpc.getNpc())) { System.out.println("Successfully hovered over spirit animal"); } else { System.out.println("Failed to hover over spirit animal"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java index a34e4c623b..a88f8a8953 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java @@ -18,7 +18,7 @@ public class CoordinateUtils { * @return player's current WorldPoint */ public static WorldPoint getPlayerLocation() { - return Microbot.getClient().getLocalPlayer().getWorldLocation(); + return Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java index d8cf6278c6..857cea3905 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java @@ -3,13 +3,16 @@ import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.valetotems.enums.GameObjectId; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * Utility class for interacting with game objects in the Vale Totems minigame @@ -17,7 +20,10 @@ * Updated to use string-based searching for totem sites and offerings */ public class GameObjectUtils { - + + private static final java.util.Set discoveredTotemIds = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private static final java.util.Set discoveredOfferingIds = java.util.concurrent.ConcurrentHashMap.newKeySet(); + // Cache configuration private static final long CACHE_EXPIRY_MS = 10000; // 10 seconds cache expiry private static final int MAX_CACHE_SIZE = 10; // Prevent memory leaks @@ -63,7 +69,9 @@ private static GameObject getCachedObjectAtLocationByName(String searchTerm, Wor // Cache miss or expired - perform expensive search Microbot.log("Cache MISS for location: " + location + " (searchTerm: " + searchTerm + ") - performing search"); long startTime = System.currentTimeMillis(); - GameObject gameObject = Rs2GameObject.getGameObject(searchTerm, false, location); + Rs2TileObjectModel tileObjModel = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)); + GameObject gameObject = toGameObject(tileObjModel); long searchTime = System.currentTimeMillis() - startTime; Microbot.log("Search completed in " + searchTime + "ms for location: " + location); @@ -126,13 +134,31 @@ public static String getCacheStats() { return String.format("Cache: %d total entries, %d expired", totalEntries, expiredEntries); } + private static GameObject toGameObject(Rs2TileObjectModel model) { + if (model == null) return null; + int targetId = model.getId(); + int wx = model.getWorldLocation().getX(); + int wy = model.getWorldLocation().getY(); + return Microbot.getClientThread().invoke(() -> { + var tile = net.runelite.client.plugins.microbot.util.tile.Rs2Tile.getTile(wx, wy); + if (tile != null) { + for (GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == targetId) { + return go; + } + } + } + return null; + }); + } + /** * Find the nearest game object by ID * @param objectId the game object ID to search for * @return the nearest game object, or null if not found */ public static GameObject findNearestObject(int objectId) { - return Rs2GameObject.getGameObject(objectId); + return toGameObject(Microbot.getRs2TileObjectCache().query().withId(objectId).nearest()); } /** @@ -141,7 +167,9 @@ public static GameObject findNearestObject(int objectId) { * @return the nearest game object, or null if not found */ public static GameObject findNearestObjectByName(String searchTerm) { - return Rs2GameObject.getGameObject(searchTerm, false); + Rs2TileObjectModel model = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest()); + return toGameObject(model); } /** @@ -151,7 +179,7 @@ public static GameObject findNearestObjectByName(String searchTerm) { * @return the game object at that location, or null if not found */ public static GameObject findObjectAtLocation(int objectId, WorldPoint location) { - return Rs2GameObject.getGameObject(objectId, location); + return toGameObject(Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3)); } /** @@ -172,7 +200,10 @@ public static GameObject findObjectAtLocationByName(String searchTerm, WorldPoin * @return list of matching game objects */ public static List findGameObjects(int objectId, WorldPoint location, int radius) { - return Rs2GameObject.getGameObjects(obj -> obj.getId() == objectId, location, radius); + return Microbot.getRs2TileObjectCache().query().withId(objectId).within(location, radius).toList().stream() + .map(GameObjectUtils::toGameObject) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } /** @@ -183,7 +214,12 @@ public static List findGameObjects(int objectId, WorldPoint location * @return list of matching game objects */ public static List findGameObjectsByName(String searchTerm, WorldPoint location, int radius) { - return Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches(searchTerm, false), location, radius); + List models = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).within(location, radius).toList()); + return models.stream() + .map(GameObjectUtils::toGameObject) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } /** @@ -193,7 +229,8 @@ public static List findGameObjectsByName(String searchTerm, WorldPoi * @return true if the interaction was successful */ public static boolean interactWithObject(GameObject gameObject, String action) { - return Rs2GameObject.interact(gameObject, action); + var obj = Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).nearest(gameObject.getWorldLocation(), 1); + return obj != null && obj.click(action); } /** @@ -203,7 +240,7 @@ public static boolean interactWithObject(GameObject gameObject, String action) { * @return true if successful */ public static boolean findAndInteract(int objectId, String action) { - return Rs2GameObject.interact(objectId, action); + return Microbot.getRs2TileObjectCache().query().interact(objectId, action); } /** @@ -213,7 +250,9 @@ public static boolean findAndInteract(int objectId, String action) { * @return true if successful */ public static boolean findAndInteractByName(String searchTerm, String action) { - return Rs2GameObject.interact(searchTerm, action); + var obj = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest()); + return obj != null && obj.click(action); } /** @@ -224,8 +263,8 @@ public static boolean findAndInteractByName(String searchTerm, String action) { * @return true if successful */ public static boolean findAndInteractAtLocation(int objectId, WorldPoint location, String action) { - GameObject obj = Rs2GameObject.getGameObject(objectId, location); - return Rs2GameObject.interact(obj, action); + var obj = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3); + return obj != null && obj.click(action); } /** @@ -236,8 +275,9 @@ public static boolean findAndInteractAtLocation(int objectId, WorldPoint locatio * @return true if successful */ public static boolean findAndInteractAtLocationByName(String searchTerm, WorldPoint location, String action) { - GameObject obj = Rs2GameObject.getGameObject(searchTerm, false, location); - return Rs2GameObject.interact(obj, action); + var obj = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)); + return obj != null && obj.click(action); } /** @@ -247,7 +287,7 @@ public static boolean findAndInteractAtLocationByName(String searchTerm, WorldPo * @return true if the object exists at that location */ public static boolean objectExistsAtLocation(int objectId, WorldPoint location) { - return Rs2GameObject.getGameObject(objectId, location) != null; + return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null; } /** @@ -257,7 +297,8 @@ public static boolean objectExistsAtLocation(int objectId, WorldPoint location) * @return true if the object exists at that location */ public static boolean objectExistsAtLocationByName(String searchTerm, WorldPoint location) { - return Rs2GameObject.getGameObject(searchTerm, false, location) != null; + return Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)) != null; } /** @@ -266,7 +307,7 @@ public static boolean objectExistsAtLocationByName(String searchTerm, WorldPoint * @return true if the object exists anywhere nearby */ public static boolean objectExists(int objectId) { - return Rs2GameObject.exists(objectId); + return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest() != null; } /** @@ -275,11 +316,11 @@ public static boolean objectExists(int objectId) { * @return distance in tiles, or -1 if object not found */ public static int getDistanceToNearestObject(int objectId) { - GameObject obj = Rs2GameObject.getGameObject(objectId); + var obj = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); if (obj == null) { return -1; } - return obj.getWorldLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()); + return obj.getWorldLocation().distanceTo(CoordinateUtils.getPlayerLocation()); } /** @@ -288,36 +329,42 @@ public static int getDistanceToNearestObject(int objectId) { * @return the GameObjectId enum for the totem state, or null if no totem found */ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { - // Search for any totem object at the location using cached method - Microbot.log("getTotemStateAtLocation started: " + (System.currentTimeMillis())); - GameObject totem = getCachedObjectAtLocationByName(GameObjectId.TOTEM_SITE.getSearchTerm(), location); - Microbot.log("getTotemStateAtLocation finished: " + (System.currentTimeMillis())); - if (totem != null) { - // Get the ObjectComposition to access actions - try { - Microbot.log("getTotemStateAtLocation findObjectComposition started: " + (System.currentTimeMillis())); - String[] actions = Rs2GameObject.findObjectComposition(totem.getId()).getActions(); - Microbot.log("getTotemStateAtLocation findObjectComposition finished: " + (System.currentTimeMillis())); - if (actions != null) { - List actionList = Arrays.asList(actions); - - // Check for specific actions to determine totem state - if (actionList.contains("Build")) { - return GameObjectId.TOTEM_SITE; - } else if (actionList.contains("Decorate")) { - return GameObjectId.TOTEM_READY_FOR_DECORATION; - } else { - // If no Build or Decorate action, assume it's ready for carving - return GameObjectId.EMPTY_TOTEM; - } - } - } catch (Exception e) { - System.err.println("Error getting actions for totem: " + e.getMessage()); - return null; + var totemModel = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isTotemObject(obj.getId()) || discoveredTotemIds.contains(obj.getId())) + .nearest(location, 10); + + if (totemModel == null) { + totemModel = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.TOTEM_SITE.getSearchTerm()) + .nearest(location, 10)); + if (totemModel != null) { + discoveredTotemIds.add(totemModel.getId()); + Microbot.log("[ValeTotem] Discovered totem id=" + totemModel.getId() + + " name='" + totemModel.getName() + "' — future lookups will use fast ID path"); } } - return null; // No totem object found at the location. + if (totemModel == null) { + return null; + } + + try { + String[] actions = totemModel.getObjectComposition().getActions(); + if (actions != null) { + List actionList = Arrays.asList(actions); + if (actionList.contains("Build")) { + return GameObjectId.TOTEM_SITE; + } else if (actionList.contains("Decorate")) { + return GameObjectId.TOTEM_READY_FOR_DECORATION; + } else { + return GameObjectId.EMPTY_TOTEM; + } + } + } catch (Exception e) { + System.err.println("Error getting actions for totem: " + e.getMessage()); + } + return null; } /** @@ -327,16 +374,23 @@ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { * @return the GameObjectId for the offerings state, or null if no offerings found */ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, int radius) { - // Use string-based search for offerings - String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - - List nearbyObjects = Rs2GameObject.getGameObjects( - Rs2GameObject.nameMatches(offeringsSearchTerm, false), location, radius - ); - + var nearbyObjects = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isOfferingsPile(obj.getId()) || discoveredOfferingIds.contains(obj.getId())) + .within(location, radius).toList(); + + if (nearbyObjects.isEmpty()) { + nearbyObjects = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.OFFERINGS_MANY.getSearchTerm()) + .within(location, radius).toList()); + for (var obj : nearbyObjects) { + discoveredOfferingIds.add(obj.getId()); + Microbot.log("[ValeTotem] Discovered offering id=" + obj.getId() + + " name='" + obj.getName() + "' — future lookups will use fast ID path"); + } + } + if (!nearbyObjects.isEmpty()) { - // Default to assuming offerings are available since we found an offerings pile - // May need refinement to distinguish between different offering states return GameObjectId.OFFERINGS_MANY; } return null; @@ -349,12 +403,23 @@ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, in * @return the offerings game object if claimable, null otherwise */ public static GameObject findClaimableOfferings(WorldPoint location, int radius) { - // Use string-based search for offerings - String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - - return Rs2GameObject.getGameObject( - offeringsSearchTerm, false, location, radius - ); + var model = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isOfferingsPile(obj.getId()) || discoveredOfferingIds.contains(obj.getId())) + .within(location, radius).nearest(); + + if (model == null) { + model = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.OFFERINGS_MANY.getSearchTerm()) + .within(location, radius).nearest()); + if (model != null) { + discoveredOfferingIds.add(model.getId()); + Microbot.log("[ValeTotem] Discovered offering id=" + model.getId() + + " name='" + model.getName() + "' — future lookups will use fast ID path"); + } + } + + return toGameObject(model); } /** @@ -367,7 +432,7 @@ public static boolean isWithinInteractionRange(GameObject gameObject, int maxDis if (gameObject == null) { return false; } - WorldPoint playerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); + WorldPoint playerLocation = CoordinateUtils.getPlayerLocation(); return gameObject.getWorldLocation().distanceTo(playerLocation) <= maxDistance; } @@ -382,7 +447,8 @@ public static boolean waitForObjectAtLocationByName(String searchTerm, WorldPoin long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMs) { - if (Rs2GameObject.getGameObject(searchTerm, false, location) != null) { + if (Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)) != null) { return true; } @@ -408,7 +474,7 @@ public static boolean waitForObjectAtLocation(int objectId, WorldPoint location, long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMs) { - if (Rs2GameObject.getGameObject(objectId, location) != null) { + if (Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null) { return true; } @@ -458,7 +524,8 @@ public static boolean waitForTotemStateAtLocation(GameObjectId expectedState, Wo * @return true if the object is reachable */ public static boolean canReachObject(GameObject gameObject) { - return Rs2GameObject.isReachable(gameObject); + var obj = Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).nearest(); + return obj != null && obj.isReachable(); } /** @@ -467,6 +534,6 @@ public static boolean canReachObject(GameObject gameObject) { * @return true if there's line of sight */ public static boolean hasLineOfSight(GameObject gameObject) { - return Rs2GameObject.hasLineOfSight(gameObject); + return true; } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java b/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java index 10b6fef4a1..d70a3b217d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java @@ -11,7 +11,6 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -95,7 +94,7 @@ public boolean run(VarrockAnvilConfig config) { return; } - if (Rs2GameObject.interact(2097)) { + if (Microbot.getRs2TileObjectCache().query().withId(2097).interact()) { debug("Using anvil"); // Wait until anvil screen is open diff --git a/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java b/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java index f50cd3efaa..59936ac9ec 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java @@ -4,7 +4,6 @@ import net.runelite.api.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -57,7 +56,7 @@ private void takeUncleanedFinds() { currentState = State.CLEAN_FIND; return; } - if (Rs2GameObject.interact(ObjectID.DIG_SITE_SPECIMEN_ROCKS, "Take")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.DIG_SITE_SPECIMEN_ROCKS, "Take")) { sleepUntil(() -> !Rs2Inventory.isFull(), 5000); } if (Rs2Inventory.isFull()) { @@ -66,7 +65,7 @@ private void takeUncleanedFinds() { } private void cleanFinds() { - if (Rs2Inventory.contains("Uncleaned find") && Rs2GameObject.interact(ObjectID.SPECIMEN_TABLE_24556, "Clean")) { + if (Rs2Inventory.contains("Uncleaned find") && Microbot.getRs2TileObjectCache().query().interact(ObjectID.SPECIMEN_TABLE_24556, "Clean")) { sleepUntil(() -> !Rs2Inventory.contains("Uncleaned find"), 30000); if (!Rs2Inventory.contains("Uncleaned find")) { currentState = State.STORAGE_CRATE; @@ -87,7 +86,7 @@ private void storeFindsInCrate() { "Arrowheads" }; - if (!Rs2Inventory.contains("Uncleaned find") && Rs2GameObject.interact(ObjectID.STORAGE_CRATE, "Add finds")) { + if (!Rs2Inventory.contains("Uncleaned find") && Microbot.getRs2TileObjectCache().query().interact(ObjectID.STORAGE_CRATE, "Add finds")) { Rs2Keyboard.keyPress('2'); sleep(1000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java index 3aa6e21e42..8e58ec4794 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java @@ -1,13 +1,13 @@ package net.runelite.client.plugins.microbot.virewatch; import net.runelite.api.Client; +import net.runelite.api.NPC; import net.runelite.api.Perspective; import net.runelite.api.TileObject; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -16,7 +16,6 @@ import javax.inject.Inject; import java.awt.*; -import java.util.stream.Collectors; import static net.runelite.client.ui.overlay.OverlayUtil.renderPolygon; @@ -77,7 +76,8 @@ public Dimension render(Graphics2D graphics) { } if (!config.disableNPCOutline()) { - for (net.runelite.api.NPC npc : Rs2Npc.getAttackableNpcs("Vyrewatch Sentinel").collect(Collectors.toList())) { + for (var npcModel : Microbot.getRs2NpcCache().query().withName("Vyrewatch Sentinel").where(n -> !n.isDead()).toList()) { + NPC npc = npcModel.getNpc(); if (npc != null && npc.getCanvasTilePoly() != null) { if (!plugin.fightArea.contains(npc.getWorldLocation())) continue; diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java index f10d022384..403ae30d68 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java @@ -4,7 +4,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -34,13 +33,13 @@ public boolean run(PVirewatchKillerConfig config, PVirewatchKillerPlugin plugin) if(Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) <= config.prayAt()) { plugin.rechargingPrayer = true; - var statue = Rs2GameObject.getGameObject(39234); + var statue = Microbot.getRs2TileObjectCache().query().withId(39234).nearest(); if(statue != null) { Rs2Walker.walkTo(statue.getWorldLocation(), 1); - sleepUntil(() -> Rs2GameObject.hasLineOfSight(statue)); - if(Rs2GameObject.hasLineOfSight(statue)) { + sleepUntil(statue::isReachable); + if(statue.isReachable()) { Microbot.status = "RECHARGING PRAYER"; - Rs2GameObject.interact(39234); + statue.click(); sleep(100); plugin.rechargingPrayer = false; if(Rs2Player.isInteracting()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java index 9bbcd587a1..3f501f078a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java @@ -32,7 +32,7 @@ @Slf4j public class VorkathPlugin extends Plugin { - public static final String version = "1.3.12"; + public static final String version = "1.3.14"; @Inject Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java index 39dbcca727..552537f7c3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java @@ -14,12 +14,14 @@ import net.runelite.client.plugins.loottracker.LootTrackerRecord; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grandexchange.Rs2GrandExchange; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; @@ -28,8 +30,6 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -100,15 +100,15 @@ private static void drinkPrayer() { } private void calculateState() { - if (Rs2Npc.getNpc(NpcID.VORKATH) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() != null) { state = State.FIGHT_VORKATH; return; } - if (Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first() != null) { state = State.PREPARE_FIGHT; return; } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) != null) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() != null) { state = State.WALK_TO_VORKATH; return; } @@ -116,7 +116,7 @@ private void calculateState() { state = State.WALK_TO_VORKATH_ISLAND; return; } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null) { state = State.WALK_TO_VORKATH; } } @@ -208,8 +208,11 @@ public boolean run() { if (config.pohInRellekka()) { teleToPoh(); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); - Rs2GameObject.interact(4525, "Enter"); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); + Rs2TileObjectModel pohPortal = Microbot.getRs2TileObjectCache().query().withId(4525).first(); + if (pohPortal != null) { + pohPortal.click("Enter"); + } sleepUntil(this::isCloseToRelleka); } else { @@ -224,24 +227,24 @@ public boolean run() { case WALK_TO_VORKATH_ISLAND: Rs2Player.toggleRunEnergy(true); Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - var torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); + Rs2NpcModel torfin = Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_RELLEKKA).first(); if (torfin != null) { - Rs2Npc.interact(torfin, "Ungael"); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null); + torfin.click("Ungael"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null); } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null) { state = State.WALK_TO_VORKATH; } break; case WALK_TO_VORKATH: kcPerTrip = 0; Rs2Walker.walkTo(new WorldPoint(2272, 4052, 0)); - TileObject iceChunks = Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE); + Rs2TileObjectModel iceChunks = Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first(); if (iceChunks != null) { - Rs2GameObject.interact(ObjectID.UNGAEL_CRATER_ENTRANCE, "Climb-over"); - sleepUntil(() -> Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null); + iceChunks.click("Climb-over"); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() == null); } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() == null) { state = State.PREPARE_FIGHT; } break; @@ -251,14 +254,20 @@ public boolean run() { boolean result = drinkPotions(); if (result) { - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); - Rs2Player.waitForWalking(); - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); + Rs2NpcModel sleepingVorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); + if (sleepingVorkath != null) { + sleepingVorkath.click("Poke"); + Rs2Player.waitForWalking(); + sleepingVorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); + if (sleepingVorkath != null) { + sleepingVorkath.click("Poke"); + } + } Rs2Player.waitForAnimation(1000); walkToCenter(); Rs2Player.waitForWalking(); handlePrayer(); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.VORKATH) != null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() != null); if (doesProjectileExistById(redProjectileId)) { handleRedBall(); sleep(300); @@ -267,7 +276,7 @@ public boolean run() { } break; case FIGHT_VORKATH: - vorkath = Rs2Npc.getNpc(NpcID.VORKATH); + vorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first(); if (vorkath == null || vorkath.isDead()) { vorkathSessionKills++; tempVorkathKills--; @@ -276,7 +285,7 @@ public boolean run() { sleep(300, 600); Rs2Inventory.wield(primaryBolts); togglePrayer(false); - sleepUntil(() -> Rs2GroundItem.exists("Superior dragon bones", 20), 15000); + sleepUntil(() -> Microbot.getRs2TileItemCache().query().withName("Superior dragon bones").within(20).first() != null, 15000); return; } if (Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) <= 0) { @@ -295,7 +304,7 @@ public boolean run() { } } - if (Rs2Npc.attack(vorkath)) + if (!Rs2Combat.inCombat() && vorkath.click("Attack")) sleep(600); if (Microbot.getClient().getLocalPlayer().getLocalLocation().getSceneY() >= 59) { walkToCenter(); @@ -319,24 +328,26 @@ public boolean run() { } break; case ZOMBIE_SPAWN: - if (Rs2Npc.getNpc(NpcID.VORKATH) == null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() == null) { state = State.FIGHT_VORKATH; } togglePrayer(false); Rs2Player.eatAt(80); drinkPrayer(); - NPC zombieSpawn = Rs2Npc.getNpc(ZOMBIFIED_SPAWN); + Rs2NpcModel zombieSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread(); if (zombieSpawn != null) { - while (Rs2Npc.getNpc(ZOMBIFIED_SPAWN) != null && !Rs2Npc.getNpc(ZOMBIFIED_SPAWN).isDead() + Rs2NpcModel currentSpawn; + while ((currentSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread()) != null + && !currentSpawn.isDead() && !doesProjectileExistById(146)) { - Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, zombieSpawn); + Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, currentSpawn); sleep(600); } Rs2Player.eatAt(75); togglePrayer(true); Rs2Tab.switchTo(InterfaceTab.INVENTORY); state = State.FIGHT_VORKATH; - sleepUntil(() -> Rs2Npc.getNpc("Zombified Spawn") == null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread() == null); if (doesProjectileExistById(redProjectileId)) { handleRedBall(); sleep(300); @@ -369,7 +380,10 @@ public boolean run() { false ); - Rs2GroundItem.loot("Vorkath's head", 20); + var vorkathHead = Microbot.getRs2TileItemCache().query().withName("Vorkath's head").within(20).first(); + if (vorkathHead != null) { + vorkathHead.pickup(); + } Rs2GroundItem.lootItemBasedOnValue(valueParams); int foodInventorySize = Rs2Inventory.getInventoryFood().size(); boolean hasVenom = Rs2Inventory.hasItem("venom"); @@ -377,7 +391,8 @@ public boolean run() { boolean hasPrayerPotion = Rs2Inventory.hasItem(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new)); boolean hasRangePotion = Rs2Inventory.hasItem(Rs2Potion.getRangePotionsVariants().toArray(String[]::new)); sleep(600, 2000); - if (!Rs2GroundItem.isItemBasedOnValueOnGround(config.priceOfItemsToLoot(), 20) && !Rs2GroundItem.exists("Vorkath's head", 20)) { + if (!Rs2GroundItem.isItemBasedOnValueOnGround(config.priceOfItemsToLoot(), 20) + && Microbot.getRs2TileItemCache().query().withName("Vorkath's head").within(20).first() == null) { if (config.KillsPerTrip() > 0 && kcPerTrip >= config.KillsPerTrip()) { leaveVorkath(); } @@ -402,9 +417,9 @@ public boolean run() { case DEAD_WALK: if (isCloseToRelleka()) { Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); + torfin = Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_RELLEKKA).first(); if (torfin != null) { - Rs2Npc.interact(torfin, "Collect"); + torfin.click("Collect"); sleepUntil(() -> Rs2Widget.hasWidget("Retrieval Service"), 1500); if (Rs2Widget.hasWidget("I'm afraid I don't have anything")) { // this means we looted all our stuff leaveVorkath(); @@ -510,10 +525,16 @@ private void leaveVorkath() { case JEWELLERY_BOX: teleToPoh(); if (config.rejuvinationPool()) { - Rs2GameObject.interact(29241, "Drink"); - sleepUntil(() -> Rs2Player.isFullHealth()); + Rs2TileObjectModel pool = Microbot.getRs2TileObjectCache().query().withId(29241).first(); + if (pool != null) { + pool.click("Drink"); + sleepUntil(() -> Rs2Player.isFullHealth()); + } + } + Rs2TileObjectModel jewelleryBox = Microbot.getRs2TileObjectCache().query().withId(29156).first(); + if (jewelleryBox != null) { + jewelleryBox.click("Teleport Menu"); } - Rs2GameObject.interact(29156, "Teleport Menu"); sleepUntil(() -> Rs2Widget.hasWidget("Castle Wars")); Rs2Widget.clickWidget("Castle Wars"); break; @@ -559,7 +580,10 @@ public void togglePrayer(boolean onOff) { private void handleRedBall() { if (doesProjectileExistById(redProjectileId)) { redBallWalk(); - Rs2Npc.interact("Vorkath", "attack"); + Rs2NpcModel vorkathNpc = Microbot.getRs2NpcCache().query().withName("Vorkath").firstOnClientThread(); + if (vorkathNpc != null) { + vorkathNpc.click("Attack"); + } } } @@ -585,11 +609,11 @@ private boolean isCloseToRelleka() { private boolean teleToPoh() { if (Rs2Magic.canCast(MagicAction.TELEPORT_TO_HOUSE)) { Rs2Magic.cast(MagicAction.TELEPORT_TO_HOUSE); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); return true; } else if (Rs2Inventory.hasItem("Teleport to house")) { Rs2Inventory.interact("Teleport to house", "break"); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); return true; } return false; @@ -626,13 +650,15 @@ private void handleAcidWalk() { boolean hasAcidProj = doesProjectileExistById(acidProjectileId); boolean hasRedAcidProj = doesProjectileExistById(acidRedProjectileId); - List acidTiles = new ArrayList<>(); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.VORKATH_ACID).forEach(o -> acidTiles.add(o.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.OLM_ACID_POOL).forEach(o -> acidTiles.add(o.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.MYQ5_ACID_POOL).forEach(o -> acidTiles.add(o.getWorldLocation())); + List acidTiles = Microbot.getRs2TileObjectCache().query() + .withIds(ObjectID.VORKATH_ACID, ObjectID.OLM_ACID_POOL, ObjectID.MYQ5_ACID_POOL) + .toList() + .stream() + .map(Rs2TileObjectModel::getWorldLocation) + .collect(Collectors.toCollection(ArrayList::new)); WorldPoint playerLoc = Microbot.getClient().getLocalPlayer().getWorldLocation(); - WorldPoint vorkathLoc = vorkath.getRuneliteNpc().getWorldLocation(); + WorldPoint vorkathLoc = vorkath.getWorldLocation(); return new Object[]{hasAcidProj, hasRedAcidProj, acidTiles, playerLoc, vorkathLoc}; }); @@ -645,7 +671,7 @@ private void handleAcidWalk() { WorldPoint vorkathLoc = (WorldPoint) clientState[4]; if (!hasAcidProj && !hasRedAcidProj && acidTiles.isEmpty()) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); state = State.FIGHT_VORKATH; acidPools.clear(); return; @@ -657,7 +683,7 @@ private void handleAcidWalk() { if (safeTile != null) { if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); } else { Rs2Player.eatAt(60); Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); @@ -666,11 +692,11 @@ private void handleAcidWalk() { } private void testWooxWalk() { - vorkath = Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING); + vorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); Object[] clientState = Microbot.getClientThread().invoke(() -> { WorldPoint playerLoc = Microbot.getClient().getLocalPlayer().getWorldLocation(); - WorldPoint vorkathLoc = vorkath.getRuneliteNpc().getWorldLocation(); + WorldPoint vorkathLoc = vorkath.getWorldLocation(); return new Object[]{playerLoc, vorkathLoc}; }); @@ -681,7 +707,7 @@ private void testWooxWalk() { if (safeTile != null) { if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); } else { Rs2Player.eatAt(60); Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java index c789d9a05f..0b0b781e9a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java @@ -10,7 +10,6 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldArea; -import net.runelite.api.TileObject; import net.runelite.client.plugins.microbot.*; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; @@ -19,7 +18,7 @@ import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.globval.WidgetIndices; @@ -80,7 +79,7 @@ public final class WildernessAgilityScript extends Script { private int dispenserLootAttempts = 0; private int dispenserTicketsBefore = 0; private int dispenserPreValue = 0; - private TileObject cachedDispenserObj = null; + private Rs2TileObjectModel cachedDispenserObj = null; private long lastObjectCheck = 0; // --- Rock Climbing Pose Detection --- @@ -258,13 +257,13 @@ public boolean run(WildernessAgilityConfig config) { waitingForRockClimbCompletion = false; // Now interact with dispenser - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; if (dispenser != null) { dispenserTicketsBefore = Rs2Inventory.itemQuantity(TICKET_ITEM_ID); dispenserPreValue = getInventoryValue(); dispenserLootAttempts = 1; waitingForDispenserLoot = true; - Rs2GameObject.interact(dispenser, "Search"); + dispenser.click("Search"); } } } @@ -501,30 +500,29 @@ public void setPlugin(WildernessAgilityPlugin plugin) { this.plugin = plugin; } - private TileObject getDispenserObj() { - return Rs2GameObject.getAll(o -> o.getId() == DISPENSER_ID, 104).stream().findFirst().orElse(null); + private Rs2TileObjectModel getDispenserObj() { + return Microbot.getRs2TileObjectCache().query().withId(DISPENSER_ID).nearest(); } - private TileObject getObstacleObj(int index) { - return Rs2GameObject.getAll(o -> o.getId() == obstacles.get(index).getObjectId(), 104).stream().findFirst().orElse(null); + private Rs2TileObjectModel getObstacleObj(int index) { + return Microbot.getRs2TileObjectCache().query().withId(obstacles.get(index).getObjectId()).nearest(); } private boolean isInUndergroundPit() { - // Check for the underground object that only exists in the pit - return Rs2GameObject.getAll(o -> o.getId() == UNDERGROUND_OBJECT_ID, 104).stream().findFirst().orElse(null) != null; + return Microbot.getRs2TileObjectCache().query().withId(UNDERGROUND_OBJECT_ID).nearest() != null; } private void recoverFromPit() { // First check if we're still in the pit using game object detection if (isInUndergroundPit()) { // Immediately refresh ladder object before attempting to interact - List ladders = Rs2GameObject.getAll(o -> o.getId() == 17385, 104); - TileObject ladderObj = ladders.isEmpty() ? null : ladders.get(0); + List ladders = Microbot.getRs2TileObjectCache().query().withId(17385).toList(); + Rs2TileObjectModel ladderObj = ladders.isEmpty() ? null : ladders.get(0); long now = System.currentTimeMillis(); if (ladderObj != null && Rs2Player.getWorldLocation().distanceTo(ladderObj.getWorldLocation()) <= 50) { // Only attempt to interact with the ladder every 2 seconds if (now - lastLadderInteractTime > 2000) { // Refresh ladder object again just before interaction - List laddersNow = Rs2GameObject.getAll(o -> o.getId() == 17385, 104); + List laddersNow = Microbot.getRs2TileObjectCache().query().withId(17385).toList(); ladderObj = laddersNow.isEmpty() ? null : laddersNow.get(0); - Rs2GameObject.interact(ladderObj, "Climb-up"); + ladderObj.click("Climb-up"); lastLadderInteractTime = now; } } @@ -548,11 +546,11 @@ private void recoverFromPit() { sleep(300, 600); // Now interact with rope - TileObject rope = getObstacleObj(1); + Rs2TileObjectModel rope = getObstacleObj(1); if (rope != null && !Rs2Player.isMoving()) { isWaitingForRope = false; ropeStartXp = Microbot.getClient().getSkillExperience(AGILITY); - boolean interacted = Rs2GameObject.interact(rope); + boolean interacted = rope.click(); if (interacted) { isWaitingForRope = true; } @@ -561,11 +559,11 @@ private void recoverFromPit() { case LOG: // Wide range detection for log, just like ladder detection - List logs = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(3).getObjectId(), 104); - TileObject log = logs.isEmpty() ? null : logs.get(0); + Rs2TileObjectModel log = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(3).getObjectId()).nearest(); if (log != null) { isWaitingForLog = false; - boolean interacted = Rs2GameObject.interact(log); + boolean interacted = log.click(); if (interacted) { isWaitingForLog = true; sleep(300, 600); @@ -620,13 +618,14 @@ private void handlePipe() { return; } // Find the pipe object at the exact tile (3004, 3938, 0) - TileObject pipe = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(0).getObjectId() && - o.getWorldLocation().equals(pipeTile), 10) - .stream().findFirst().orElse(null); + Rs2TileObjectModel pipe = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(0).getObjectId()) + .where(o -> o.getWorldLocation().equals(pipeTile)) + .nearest(); if (pipe == null) { return; } - boolean interacted = Rs2GameObject.interact(pipe); + boolean interacted = pipe.click(); if (interacted) { isWaitingForPipe = true; pipeJustCompleted = true; // Set immediately after interaction @@ -661,9 +660,9 @@ private void handleRope() { } } if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !isWaitingForRope) { - TileObject rope = getObstacleObj(1); + Rs2TileObjectModel rope = getObstacleObj(1); if (rope != null) { - boolean interacted = Rs2GameObject.interact(rope); + boolean interacted = rope.click(); if (interacted) { isWaitingForRope = true; ropeStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -715,9 +714,9 @@ private void handleStones() { // Only attempt interaction if not already waiting and not animating/moving if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !isWaitingForStones) { WorldPoint loc = Rs2Player.getWorldLocation(); - TileObject stones = getObstacleObj(2); + Rs2TileObjectModel stones = getObstacleObj(2); if (stones != null) { - boolean interacted = Rs2GameObject.interact(stones); + boolean interacted = stones.click(); if (interacted) { isWaitingForStones = true; stonesStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -756,13 +755,13 @@ private void handleLog() { clearInventoryIfNeeded(); } - TileObject log = getObstacleObj(3); + Rs2TileObjectModel log = getObstacleObj(3); if (log == null) { - List logs = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(3).getObjectId(), 104); - log = logs.isEmpty() ? null : logs.get(0); + log = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(3).getObjectId()).nearest(); } if (log != null) { - boolean interacted = Rs2GameObject.interact(log); + boolean interacted = log.click(); if (interacted) { isWaitingForLog = true; logStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -783,7 +782,7 @@ private void handleRocks() { WorldPoint loc = Rs2Player.getWorldLocation(); if (loc != null && loc.getY() <= 3933) { // Get fresh dispenser object for immediate use - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); @@ -795,7 +794,7 @@ private void handleRocks() { dispenserPreValue = getInventoryValue(); dispenserLootAttempts = 1; waitingForDispenserLoot = true; - Rs2GameObject.interact(freshDispenser, "Search"); + freshDispenser.click("Search"); } return; } @@ -807,7 +806,9 @@ private void handleRocks() { WorldPoint targetRock = new Random().nextBoolean() ? rock1 : rock2; - if (Rs2GameObject.interact(targetRock, "Climb")) { + Rs2TileObjectModel targetRockObj = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(targetRock)).nearest(); + if (targetRockObj != null && targetRockObj.click("Climb")) { // Monitor Y coordinate in real-time for immediate transition boolean transitioned = sleepUntil(() -> { WorldPoint currentLoc = Rs2Player.getWorldLocation(); @@ -819,7 +820,7 @@ private void handleRocks() { if (transitioned) { // Immediate transition to dispenser - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); currentState = ObstacleState.DISPENSER; @@ -839,7 +840,7 @@ private void handleRocks() { int startExp = Microbot.getClient().getSkillExperience(AGILITY); if (waitForXpChange(startExp, 3000)) { // Shorter timeout for fallback Microbot.log("[WildernessAgility] XP fallback successful, transitioning to dispenser"); - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); currentState = ObstacleState.DISPENSER; @@ -866,7 +867,7 @@ private void handleRocks() { } } private void handleDispenser() { - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; WorldPoint playerLoc = Rs2Player.getWorldLocation(); if (dispenser == null || playerLoc == null) return; if (playerLoc.distanceTo(dispenser.getWorldLocation()) > 20) return; @@ -915,7 +916,7 @@ private void handleDispenser() { if (dispenserLootAttempts == 0) { dispenserPreValue = getInventoryValue(); dispenserTicketsBefore = currentTickets; - Rs2GameObject.interact(dispenser, "Search"); + dispenser.click("Search"); waitingForDispenserLoot = true; dispenserLootAttempts = 1; // Only try once, now wait for loot } else if (dispenserLootAttempts == 1) { @@ -924,13 +925,13 @@ private void handleDispenser() { } } private void handleConfigChecks() { - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; if (dispenser == null) return; int ticketCount = Rs2Inventory.itemQuantity(TICKET_ITEM_ID); if (ticketCount >= config.useTicketsWhen()) { boolean didInteract = Rs2Inventory.interact(TICKET_ITEM_ID, "Use"); if (didInteract) { - didInteract = Rs2GameObject.interact(dispenser, "Use"); + didInteract = dispenser != null && dispenser.click("Use"); if (didInteract) { sleepUntil(() -> Rs2Inventory.itemQuantity(TICKET_ITEM_ID) < ticketCount, 2000); } @@ -1034,7 +1035,7 @@ private void handleStart() { // DISABLED: This corrupts inventory action data and causes Rs2Inventory.use() to crash // checkLootingBagOnStartup(); - TileObject dispenserObj = getDispenserObj(); + Rs2TileObjectModel dispenserObj = getDispenserObj(); WorldPoint playerLoc = Rs2Player.getWorldLocation(); boolean nearDispenser = dispenserObj != null && playerLoc != null && playerLoc.distanceTo(dispenserObj.getWorldLocation()) <= 4; @@ -1075,7 +1076,7 @@ private void handleStart() { Microbot.log("[WildernessAgility] Attempting to deposit " + coinCount + " coins into dispenser"); Rs2Inventory.use(COINS_ID); sleep(400); - Rs2GameObject.interact(dispenserObj, "Use"); + dispenserObj.click("Use"); sleep(getActionDelay()); sleepUntil(() -> Rs2Inventory.itemQuantity(COINS_ID) < coinCount, getXpTimeout()); } else { @@ -1482,7 +1483,7 @@ private void handleEmergencyEscape() { if (isInArea) { Microbot.log("[WildernessAgility] Emergency Escape Step 2: Climbing rocks"); - Rs2GameObject.interact(ROCKS_OBJECT_ID, "Climb"); // Climb rocks + Microbot.getRs2TileObjectCache().query().interact(ROCKS_OBJECT_ID, "Climb"); // Climb rocks sleep(1200); sleepUntil(() -> !Rs2Player.isMoving(), 5000); hasClimbedRocks = true; @@ -1506,8 +1507,8 @@ private void handleEmergencyEscape() { // Step 3: Open gate (Netoxic's approach) - only if not already opened if (!hasOpenedGate) { Microbot.log("[WildernessAgility] Emergency Escape Step 3: Opening gate"); - sleepUntilOnClientThread(() -> Rs2GameObject.getGameObject(GATE_OBJECT_ID) != null); // Wait for Gate - Rs2GameObject.interact(GATE_OBJECT_ID, "Open"); + sleepUntilOnClientThread(() -> Microbot.getRs2TileObjectCache().query().withId(GATE_OBJECT_ID).nearest() != null); // Wait for Gate + Microbot.getRs2TileObjectCache().query().interact(GATE_OBJECT_ID, "Open"); hasOpenedGate = true; return; // Wait for next loop iteration } @@ -1774,14 +1775,14 @@ private void handleWalkToCourse() { sleepUntil(() -> isAt(START_POINT, 2), 20000); return; } - TileObject dispenserObj = getDispenserObj(); + Rs2TileObjectModel dispenserObj = getDispenserObj(); if (dispenserObj != null) { int coinCount = Rs2Inventory.itemQuantity(COINS_ID); if (coinCount >= 150000) { Microbot.log("[WildernessAgility] [WALK_TO_COURSE] Attempting to deposit " + coinCount + " coins into dispenser"); Rs2Inventory.use(COINS_ID); sleep(400); - Rs2GameObject.interact(dispenserObj, "Use"); + dispenserObj.click("Use"); sleep(getActionDelay()); sleepUntil(() -> Rs2Inventory.itemQuantity(COINS_ID) < coinCount, getXpTimeout()); } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java index b415ae5bdb..a50bc1e368 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java @@ -15,6 +15,7 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -51,7 +52,7 @@ public class MWintertodtScript extends Script { final WorldPoint sproutingRoots = new WorldPoint(1635, 3978, 0); String axe = ""; int wintertodtHp = -1; - private GameObject brazier; + private Rs2TileObjectModel brazierModel; boolean init = false; private static void changeState(State scriptState) { @@ -139,9 +140,12 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { boolean wintertodtRespawning = Rs2Widget.hasWidget("returns in"); boolean isWintertodtAlive = Rs2Widget.hasWidget("Wintertodt's Energy"); - brazier = Rs2GameObject.findObject(BRAZIER_29312, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - GameObject brokenBrazier = Rs2GameObject.findObject(ObjectID.BRAZIER_29313, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - GameObject fireBrazier = Rs2GameObject.findObject(ObjectID.BURNING_BRAZIER_29314, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); + brazierModel = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29312) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + Rs2TileObjectModel brokenBrazierModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.BRAZIER_29313) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + Rs2TileObjectModel fireBrazierModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.BURNING_BRAZIER_29314) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); boolean playerIsLowWarmth = getWarmthLevel() < config.warmthTreshhold(); // if use rejuvenation potion is enabled, we should check for potions instead of food boolean needBanking = !Rs2Inventory.hasItemAmount(config.rejuvenationPotions() ? "Rejuvenation potion " : config.food().getName(), config.minFood(), false, false) @@ -163,7 +167,7 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { } dropUnnecessaryItems(); - shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazier, brazier); + shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazierModel, brazierModel); shouldBank(needBanking); shouldEat(); dodgeOrbDamage(); @@ -208,11 +212,11 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { case WAITING: walkToBrazier(); - shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazier, brazier); + shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazierModel, brazierModel); break; case LIGHT_BRAZIER: - if (brazier != null && !Rs2Player.isAnimating()) { - if (Rs2GameObject.interact(brazier, "light")) { + if (brazierModel != null && !Rs2Player.isAnimating()) { + if (brazierModel.click("light")) { sleepGaussian(600, 150); } return; @@ -225,7 +229,7 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { Rs2Combat.setSpecState(true, 1000); } if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(ObjectID.BRUMA_ROOTS, "Chop"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BRUMA_ROOTS, "Chop"); sleepUntil(Rs2Player::isAnimating, 2000); resetActions = false; Rs2Antiban.actionCooldown(); @@ -251,31 +255,30 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { break; case BURN_LOGS: if (!Microbot.isGainingExp || resetActions) { - TileObject burningBrazier = Rs2GameObject.findObjectById(BURNING_BRAZIER_29314); - if (brokenBrazier != null && config.fixBrazier()) { - Rs2GameObject.interact(brokenBrazier, "fix"); + Rs2TileObjectModel burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314).nearest(); + if (brokenBrazierModel != null && config.fixBrazier()) { + brokenBrazierModel.click("fix"); Microbot.log("Fixing brazier"); sleepGaussian(300, 50); return; } - // this extra check is needed in case all braziers are broken or not burning - if (burningBrazier == null && brazier != null && config.relightBrazier()) { + if (burningBrazier == null && brazierModel != null && config.relightBrazier()) { - Rs2GameObject.interact(brazier, "light"); + brazierModel.click("light"); Microbot.log("Lighting brazier"); sleep(1500); return; } else { - if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 10 && brazier != null && config.relightBrazier()) { - Rs2GameObject.interact(brazier, "light"); + if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 10 && brazierModel != null && config.relightBrazier()) { + brazierModel.click("light"); Microbot.log("Lighting brazier"); sleep(1500); return; } } if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 10 && hasItemsToBurn()) { - Rs2GameObject.interact(burningBrazier, "feed"); + burningBrazier.click("feed"); Microbot.log("Feeding brazier"); resetActions = false; sleep(GAME_TICK_LENGTH * 3); @@ -358,7 +361,7 @@ private boolean shouldChopRoots() { return true; } - private boolean shouldLightBrazier(boolean isWintertodtAlive, boolean needBanking, GameObject fireBrazier, GameObject brazier) { + private boolean shouldLightBrazier(boolean isWintertodtAlive, boolean needBanking, Rs2TileObjectModel fireBrazier, Rs2TileObjectModel brazier) { if (!isWintertodtAlive) return false; if (needBanking) return false; if (state == State.CHOP_ROOTS) return false;// we are most likely to far from the brazier to light it in time @@ -410,7 +413,9 @@ private void walkToBrazier() { // //sleep(3000); // } } else if (Rs2Player.getWorldLocation().equals(config.brazierLocation().getBRAZIER_LOCATION()) && state == State.WAITING) { - Rs2GameObject.hoverOverObject(brazier); + if (brazierModel != null && brazierModel.getClickbox() != null) { + Microbot.getMouse().move(brazierModel.getClickbox().getBounds()); + } } } @@ -420,7 +425,7 @@ private void dodgeOrbDamage() { && WorldPoint.fromLocalInstance(Microbot.getClient(), graphicsObject.getLocation()).distanceTo(Rs2Player.getWorldLocation()) == 1) { //walk south - List gameObjects = new ArrayList<>(Rs2GameObject.getGameObjects(5)); + List gameObjects = Microbot.getRs2TileObjectCache().query().within(5).toList(); // we only need to dodge if there are 2 or more snow fall objects if (gameObjects.size() > 2) { Rs2Walker.walkFastCanvas(new WorldPoint(Rs2Player.getWorldLocation().getX(), Rs2Player.getWorldLocation().getY() - 1, Rs2Player.getWorldLocation().getPlane())); @@ -444,16 +449,19 @@ private boolean handleBankLogic(MWintertodtConfig config) { Rs2Walker.walkTo(crateLocation, 3); Rs2Player.waitForWalking(1000); } - GameObject crate = Rs2GameObject.getGameObject(crateLocation); + Rs2TileObjectModel crate = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(crateLocation)).nearest(); if (crate != null) { + final Rs2TileObjectModel crateRef = crate; sleepUntil(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) >= config.foodAmount(), () -> { - if (Rs2GameObject.interact(crate, "Take-concoction")) + if (crateRef.click("Take-concoction")) Rs2Inventory.waitForInventoryChanges(3000); }, 10000, 300); } - GameObject roots = Rs2GameObject.getGameObject(sproutingRoots); + Rs2TileObjectModel roots = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(sproutingRoots)).nearest(); if (roots != null) { - Rs2GameObject.interact(roots, "Pick"); + roots.click("Pick"); Rs2Inventory.waitForInventoryChanges(5000); sleepUntil(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) <= Rs2Inventory.count(ItemID.BRUMA_HERB), 10000); Rs2Inventory.combineClosest(ItemID.REJUVENATION_POTION_UNF, ItemID.BRUMA_HERB); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java index 56a061c4fe..97da1ac01a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java @@ -50,7 +50,7 @@ ) @Slf4j public class AutoWoodcuttingPlugin extends Plugin { - public static final String version = "1.8.2"; + public static final String version = "1.8.3"; @Inject @Getter(AccessLevel.MODULE) public AutoWoodcuttingScript autoWoodcuttingScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java index e06ae5298b..b35c04674d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java @@ -7,10 +7,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -34,10 +31,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var forester = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - return forester.isPresent(); + var forester = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) + .nearest(); + return forester != null; } catch (Exception e) { log.error("EggEvent: Exception in validate method", e); return false; @@ -48,12 +45,12 @@ public boolean validate() { public boolean execute() { Microbot.log("EggEvent: Executing Egg event"); - var forester = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - if (forester.isEmpty()) { + var forester = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) + .nearest(); + if (forester == null) { Microbot.log("EggEvent: Forester not found, cannot proceed with egg event."); - return true; // If the forester is not found, we cannot proceed with the event + return true; } plugin.currentForestryEvent = ForestryEvents.PHEASANT; @@ -75,15 +72,15 @@ public boolean execute() { // If we have an egg, interact with the forester if (Rs2Inventory.contains("Pheasant egg")) { Microbot.log("EggEvent: Interacting with the forester to give the egg."); - Rs2Npc.interact(forester.get(), "Talk-to"); + forester.click("Talk-to"); sleepUntil(Rs2Dialogue::isInDialogue, 5000); while (Rs2Dialogue.isInDialogue()) Rs2Dialogue.clickContinue(); continue; } // If we don't have an egg, interact with the pheasant nest - var nests = Rs2GameObject.getGameObjects((gameObject) -> gameObject.getId() == ObjectID.GATHERING_EVENT_PHEASANT_NEST02); - var pheasants = Rs2Npc.getNpcs(NpcID.GATHERING_EVENT_PHEASANT).collect(Collectors.toList()); + var nests = Microbot.getRs2TileObjectCache().query().where(gameObject -> gameObject.getId() == ObjectID.GATHERING_EVENT_PHEASANT_NEST02).toList(); + var pheasants = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_PHEASANT).toList(); if (nests.isEmpty() || pheasants.isEmpty()) { Microbot.log("EggEvent: No pheasant nests found, cannot proceed with egg event."); @@ -91,7 +88,6 @@ public boolean execute() { } // find nest without pheasants var emptyNests = nests.stream() - .filter(Rs2GameObject::isReachable) .filter(nest -> pheasants.stream() .noneMatch(pheasant -> pheasant.getWorldLocation() == nest.getWorldLocation())) .collect(Collectors.toList()); @@ -101,7 +97,7 @@ public boolean execute() { .min(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) .orElse(null); - var interact = Rs2GameObject.interact(closestNest); + var interact = closestNest != null && closestNest.click(); if (!interact) { Microbot.log("EggEvent: Failed to interact with the pheasant nest."); Microbot.log("EggEvent: Closest nest is null? " + (closestNest == null)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java index faf46c5d17..dcb64ae7ae 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java @@ -5,15 +5,13 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import java.util.Comparator; -import java.util.stream.Collectors; @Slf4j public class EntlingsEvent implements BlockingEvent { @@ -27,8 +25,7 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var entlings = Rs2Npc.getNpcs(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) - .collect(Collectors.toList()); + var entlings = Microbot.getRs2NpcCache().query().where(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01).toList(); return !entlings.isEmpty(); } catch (Exception e) { log.error("EntlingsEvent: Exception in validate method", e); @@ -49,10 +46,12 @@ public boolean execute() { } while (this.validate()) { - var entlings = Rs2Npc.getNpcs(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) - .sorted(Comparator.comparingInt(e -> + var entlings = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) + .toList(); + entlings.sort(Comparator.comparingInt(e -> e.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) - )).collect(Collectors.toList()); + )); for (Rs2NpcModel entling : entlings) { String request = entling.getOverheadText(); @@ -75,7 +74,7 @@ public boolean execute() { } Microbot.log("EntlingsEvent: Interacting with entling: with action: " + action); - Rs2Npc.interact(entling, action); + entling.click(action); Rs2Player.waitForAnimation(1000); // Wait for the pruning animation to finish } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java index e8c1ab561d..7365288254 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java @@ -5,14 +5,11 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; -import java.util.stream.Collectors; - import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j public class FlowersEvent implements BlockingEvent { @@ -27,9 +24,9 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var flowers = Rs2Npc.getNpcs(npc -> - npc.getName() != null && isFloweringBush(npc.getId()) - ).collect(Collectors.toList()); + var flowers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null && isFloweringBush(npc.getId())) + .toList(); return !flowers.isEmpty(); } catch (Exception e) { log.error("FlowersEvent: Exception in validate method", e); @@ -49,9 +46,9 @@ public boolean execute() { } log.info("FlowersEvent: Executing Flowers event"); while (this.validate()) { - var flowers = Rs2Npc.getNpcs(npc -> - npc.getName() != null && isFloweringBush(npc.getId()) - ).collect(Collectors.toList()); + var flowers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null && isFloweringBush(npc.getId())) + .toList(); if (flowers.isEmpty()) { break; @@ -69,7 +66,7 @@ public boolean execute() { continue; } - if (Rs2Npc.interact(availableFlower, "Tend-to")) { + if (availableFlower.click("Tend-to")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isInteracting(), 8000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java index dad018c756..29e874e51f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -25,8 +24,8 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var outDoorFox = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_FOX_OUTDOORS); - var indoorFox = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_FOX_INDOORS); + var outDoorFox = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_FOX_OUTDOORS).nearest(); + var indoorFox = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_FOX_INDOORS).nearest(); return outDoorFox != null || indoorFox != null; } catch (Exception e) { log.error("FoxEvent: Exception in validate method", e); @@ -47,13 +46,13 @@ public boolean execute() { } while (this.validate()) { - var trap = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_TRAP); + var trap = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_TRAP).nearest(); 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); // Interact with the trap if it exists - Rs2Npc.interact(trap, "Disarm"); + trap.click("Disarm"); Rs2Player.waitForAnimation(1000); } Microbot.log("FoxEvent: Finished executing the Fox event.", Level.INFO); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java index 78dfd756ac..90845cc50f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java @@ -5,10 +5,9 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -20,7 +19,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j @@ -40,9 +38,11 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - 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); + var beehives = Microbot.getRs2NpcCache().query() + .where(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) + .toList(); WoodcuttingTree tree = plugin.getSelectedTree(); - return beehives.findAny().isPresent() && tree != null && Rs2Inventory.count(tree.getLogID()) > 1; + return !beehives.isEmpty() && tree != null && Rs2Inventory.count(tree.getLogID()) > 1; } catch (Exception e) { log.error("HivesEvent: Exception in validate method", e); return false; @@ -68,11 +68,11 @@ public boolean execute() { } // find available beehives, excluding ones we've already completed - List availableBeehives = 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) && - !completedBeehives.contains(x.getIndex())) - .collect(Collectors.toList()); + List availableBeehives = Microbot.getRs2NpcCache().query() + .where(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) && + !completedBeehives.contains(x.getIndex())) + .toList(); if (availableBeehives.isEmpty()) { log.info("No more available beehives to work on"); @@ -105,7 +105,7 @@ public boolean execute() { } // check if beehive still exists (might have been completed by others or disappeared) - if (!Rs2Npc.getNpcs(x -> x.getIndex() == targetBeehive.getIndex()).findAny().isPresent()) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getIndex() == targetBeehive.getIndex()).count() == 0) { log.info("Beehive {} completed or disappeared", targetBeehive.getIndex()); completedBeehives.add(targetBeehive.getIndex()); currentBeehive = null; @@ -121,7 +121,7 @@ public boolean execute() { } log.info("Building beehive {} (logs: {})", targetBeehive.getIndex(), currentLogCount); - if (Rs2Npc.interact(targetBeehive, "Build")) { + if (targetBeehive.click("Build")) { // wait for interaction to start sleepUntil(() -> Rs2Player.isInteracting() || Rs2Player.isAnimating(), 3000); @@ -136,7 +136,7 @@ public boolean execute() { } // check if this beehive is now completed (disappeared) - if (!Rs2Npc.getNpcs(x -> x.getIndex() == targetBeehive.getIndex()).findAny().isPresent()) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getIndex() == targetBeehive.getIndex()).count() == 0) { log.info("Beehive {} completed successfully", targetBeehive.getIndex()); completedBeehives.add(targetBeehive.getIndex()); currentBeehive = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java index 9680f4d775..2ccc5455bb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java @@ -7,18 +7,12 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import org.slf4j.event.Level; -import java.util.Comparator; -import java.util.Optional; - import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; @Slf4j public class LeprechaunEvent implements BlockingEvent { @@ -34,10 +28,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - Optional leprechaun = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - return leprechaun.isPresent(); + var leprechaun = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN) + .nearest(); + return leprechaun != null; } catch (Exception e) { log.error("LeprechaunEvent: Exception in validate method", e); return false; @@ -51,7 +45,7 @@ public boolean execute() { Rs2Walker.setTarget(null); // stop walking, stop moving to bank for example while (this.validate()) { log.info("LeprechaunEvent: Leprechaun event still valid, continuing execution get opbject"); - var endOfRainbow = Rs2GameObject.getGameObject(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW); + var endOfRainbow = Microbot.getRs2TileObjectCache().query().withId(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW).nearest(); if (endOfRainbow == null) { log.warn("LeprechaunEvent: End of the rainbow not found, retrying..."); sleepGaussian(900, 300); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java index 1fa66c6022..1ebc0e6f93 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -13,9 +12,7 @@ import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import org.slf4j.event.Level; -import java.util.Comparator; import java.util.List; -import java.util.Optional; import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @@ -34,11 +31,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - Optional dryadCache = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - //var dryad = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD); - return dryadCache.isPresent(); + var dryad = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD) + .nearest(); + return dryad != null; } catch (Exception e) { log.error("RitualEvent: Exception in validate method", e); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java index 28f029a497..a9e95e0470 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java @@ -1,11 +1,11 @@ package net.runelite.client.plugins.microbot.woodcutting.Forestry; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -43,11 +43,12 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var strugglingSaplings = Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches("Struggling sapling", false)); - if (strugglingSaplings == null) return false; + var strugglingSaplings = Microbot.getRs2TileObjectCache().query() + .withName("Struggling sapling") + .toListOnClientThread(); if (strugglingSaplings.isEmpty()) return false; return strugglingSaplings.stream().anyMatch(obj -> - Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Add-mulch") && + Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= AutoWoodcuttingScript.FORESTRY_DISTANCE ); } catch (Exception e) { @@ -62,19 +63,22 @@ public boolean execute() { Microbot.log("StrugglingSaplingEvent: Executing Struggling Sapling event"); plugin.currentForestryEvent = ForestryEvents.STRUGGLING_SAPLING; // Find the struggling sapling - var sapling = Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches("Struggling sapling", false)) + var sapling = Microbot.getRs2TileObjectCache().query() + .withName("Struggling sapling") + .toListOnClientThread() .stream() .filter(obj -> - Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Add-mulch") && + Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= AutoWoodcuttingScript.FORESTRY_DISTANCE ) .findFirst() .orElse(null); - // Find all available leaf ingredients - var ingredients = Rs2GameObject.getGameObjects(gameObject -> ingredientIds.contains(gameObject.getId())) + var ingredients = Microbot.getRs2TileObjectCache().query() + .where(gameObject -> ingredientIds.contains(gameObject.getId())) + .toList() .stream() - .filter(obj -> Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Collect")) + .filter(obj -> Rs2GameObject.hasAction(obj.getObjectComposition(), "Collect")) .collect(Collectors.toList()); if (ingredients.isEmpty()) { @@ -97,7 +101,7 @@ public boolean execute() { // If we have mulch stage 3 in inventory, add them to the sapling if (Rs2Inventory.contains(ItemID.GATHERING_EVENT_SAPLING_MULCH_STAGE3)) { Microbot.log("StrugglingSaplingEvent: Adding mulch to the struggling sapling."); - Rs2GameObject.interact(sapling, "Add-mulch"); + sapling.click("Add-mulch"); Rs2Player.waitForAnimation(); continue; } @@ -116,11 +120,10 @@ public boolean execute() { if (correctIngredient != null) { // Look for matching ingredient in our available ingredients - for (GameObject ingredient : ingredients) { + for (Rs2TileObjectModel ingredient : ingredients) { 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"); + ingredient.click("Collect"); Rs2Player.waitForAnimation(); } } @@ -144,7 +147,7 @@ public boolean execute() { Microbot.log("StrugglingSaplingEvent: No known correct ingredient, collecting a random one."); var randomIngredient = availableIngredients.get((int) (Math.random() * availableIngredients.size())); Microbot.log("StrugglingSaplingEvent: Collecting random ingredient: " + randomIngredient.getWorldLocation()); - Rs2GameObject.interact(randomIngredient, "Collect"); + randomIngredient.click("Collect"); triedIngredients.add(randomIngredient.getId()); Rs2Player.waitForAnimation(); } diff --git a/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md new file mode 100644 index 0000000000..01c3be6482 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md @@ -0,0 +1,28 @@ +# AIO AIO — Record & Replay + +Record a sequence of in-game menu actions, then play it back on demand or on loop. 80/20 automation for one-off tasks. + +## What it does + +- Captures `MenuOptionClicked` events (NPC/object/ground-item interactions, inventory actions, widget clicks) +- Saves recordings as JSON under `~/.runelite/microbot-recordings/` +- Replays them with proper tick pacing; for inventory item actions it delegates to `Rs2Inventory.interact()` so stale slot positions don't cause misses +- For NPCs/objects/ground items it re-resolves the target by id/name each run (via the queryable cache) — your recording keeps working even if the target moves or respawns + +## Usage + +1. Enable the plugin, open the AIO AIO side panel +2. Click **Record script**, perform the actions in-game, click **Stop** when done +3. The recording shows in the **Script:** dropdown (sorted by last recorded/played time) +4. Select it, hit **Run script** — loops until you hit **Stop script** + +## Editing a recording + +- Double-click or ✎ to edit a step's pre-delay (in ticks) +- ↑ / ↓ to reorder, ⧉ to duplicate, ✕ to delete +- ✎ Rename / 🗑 Delete on the whole script + +## Limits + +- Actions whose target can't be found at replay time are skipped (see logs) +- The playback loop enables `naturalMouse` while running and restores it on exit diff --git a/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md new file mode 100644 index 0000000000..e213532d98 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md @@ -0,0 +1,43 @@ +# Butterfly Catcher +**Author:** StonksCode | **Version:** 1.0.0 + +Automates butterfly and moth catching for Hunter XP. Runs indefinitely with no banking required. + +--- + +## Supported Species + +| Species | Net Level | Barehanded Level | Location | +|---|---|---|---| +| Ruby Harvest | 5 | 15 | Puro-Puro / various | +| Sapphire Glacialis | 25 | 35 | Asgarnian Ice Dungeon area | +| Snowy Knight | 35 | 45 | Asgarnian Ice Dungeon area | +| Black Warlock | 45 | 55 | Feldip Hunter area | +| Sunlight Moth | 65 | 75 | Avium Savannah (Varlamore) | +| Moonlight Moth | 75 | 85 | Hunter Guild / Tonali Cavern (Varlamore) | + +--- + +## Setup + +1. Travel to a spawn location for your chosen species. +2. If using **Butterfly Net** mode, equip your net before starting. +3. Open the plugin config, select your species and catch mode. +4. Enable the plugin — it will run until you stop it. + +--- + +## Catch Modes + +**Barehanded** — Catch and instantly release. Nothing enters your inventory. Requires Hunter level 10 higher than the net threshold. + +**Butterfly Net** — Requires a Butterfly Net (id 10010) or Magic Butterfly Net (id 11259) to be equipped before starting. Allows catching at 10 levels lower than barehanded. + +--- + +## Notes + +- The plugin checks your Hunter level on startup and stops with a message if you don't meet the requirement. +- In Butterfly Net mode, the plugin also verifies your net is equipped before starting. +- No banking — the script runs indefinitely at your chosen spawn. +- Moonlight Moth supports all three location NPC variants (Hunter Guild, Neypotzli, Tonali Cavern). diff --git a/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md new file mode 100644 index 0000000000..224c1e9d6e --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md @@ -0,0 +1,16 @@ +# Leagues Toolkit + +Quality-of-life utilities for OSRS Leagues. + +## Features + +### Anti-AFK +Prevents the idle-timeout logout during long AFK skilling sessions (e.g. mining with the auto-bank relic where inventory never fills and no interaction happens between rock respawns). Periodically triggers a configurable input right before the client's AFK threshold so the session stays alive indefinitely. + +Configurable: +- **Input method** — random arrow key (default, most natural), backspace, or camera yaw rotation +- **Trigger buffer min/max** — how many ticks before the idle-timeout to fire input (randomized between min and max) + +## Roadmap + +More Leagues-focused utilities planned. Open an issue or PR with ideas. diff --git a/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md new file mode 100644 index 0000000000..be60ed19ae --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md @@ -0,0 +1,54 @@ +# Left-Click Cast + +Replaces the left-click **Attack** option on attackable NPCs and on players (wilderness / PvP) with a preconfigured **Cast Spell** action. The plugin stays invisible when you swap to a melee or ranged weapon, so leaving it enabled is safe. + +## How it works + +When an "Attack" menu entry is added for an NPC, the plugin inserts a new menu entry for the selected spell and places it above Attack, making the spell the left-click action. + +All casting is dispatched through the Microbot client's existing `Rs2Magic.castOn(MagicAction, Actor)` — the plugin does not re-implement rune checks, spellbook switching, or targeting. + +## Configuration + +| Option | Default | Description | +| --- | --- | --- | +| **Enabled** | `true` | Master switch. When off, no menu entries are inserted. | +| **Spell** | `Fire Strike` | Legacy single-spell setting. On startup the plugin migrates this into **Slot 1 Spell** if Slot 1 is still at its default — keeps existing configs working without any manual action. | +| **Require magic weapon** | `true` | When on, the Cast entry only shows while a staff, bladed staff, powered staff, or powered wand is equipped (detected via varbit `EQUIPPED_WEAPON_TYPE`). Disable to cast regardless of weapon. | + +### Spell slots and hotkeys + +The plugin exposes five independently configurable spell slots, grouped under two sections in the config panel: + +| Section | Options | +| --- | --- | +| **Spell Slots** | `Slot 1 Spell` … `Slot 5 Spell` — each picks any spell from the full supported-spells dropdown. All five default to `Fire Strike`. | +| **Hotkeys** | `Slot 1 Hotkey` … `Slot 5 Hotkey` — RuneLite-standard hotkey pickers, all unbound by default. `Chat feedback on slot change` — toggles the chat message posted when a hotkey switches slots (default on). | + +**How slot switching works:** + +- **Slot 1 is always active at startup.** Enabling the plugin (or restarting the client) resets the active slot to Slot 1. The active slot is runtime-only — it is never written to config. +- **Press a bound slot hotkey** (while the game window is focused and no text field is active) to make that slot the active slot. The next menu-sort uses the new slot's spell. +- **Unbound hotkeys are inert.** A slot whose hotkey is `Not set` cannot be activated by keypress. RuneLite's hotkey plumbing suppresses hotkeys while you're typing in a chat or search widget, so hotkey letters won't accidentally swap slots during text entry. +- **Slot 1 needs no hotkey.** Because it's the startup default, leave its hotkey unbound unless you want to explicitly return to it from another slot. +- **Chat feedback** (when enabled) prints `Left-Click Cast: now casting ` on every slot change. Toggle it off if it's noisy during combat rotations. + +## Limitations + +- **No rune auto-management.** If you run out of runes, the cast fails cleanly and you see the normal "You do not have enough ..." chat message. +- **No auto-spellbook switching.** If the selected spell is not on your current spellbook, the cast fails silently. Switch spellbooks manually. +- **The dropdown is the source of truth.** Spells not listed in the dropdown are not supported by this plugin. +- **Staff-only default.** With `Require magic weapon` enabled (default), non-magic weapon types produce normal Attack behavior. Disable the toggle if you want to cast from melee/ranged weapons as well. +- **Cooperative menu composition.** If another plugin inserts menu entries after this one on the same tick, its entry becomes the top entry instead — a known limitation of RuneLite's menu model. + +## Supported spells + +**Modern combat (Strike → Surge):** Wind Strike, Water Strike, Earth Strike, Fire Strike, Wind Bolt, Water Bolt, Earth Bolt, Fire Bolt, Wind Blast, Water Blast, Earth Blast, Fire Blast, Wind Wave, Water Wave, Earth Wave, Fire Wave, Wind Surge, Water Surge, Earth Surge, Fire Surge. + +**Ancient combat:** Smoke / Shadow / Blood / Ice — Rush, Burst, Blitz, Barrage. + +**Non-autocastable combat:** Crumble Undead, Iban Blast, Magic Dart, Saradomin Strike, Claws of Guthix, Flames of Zamorak. + +**Arceuus offensive:** Ghostly Grasp, Skeletal Grasp, Undead Grasp, Inferior Demonbane, Superior Demonbane, Dark Demonbane, Lesser Corruption, Greater Corruption. + +**Utility target spells:** Confuse, Weaken, Curse, Bind, Snare, Entangle, Vulnerability, Enfeeble, Stun, Tele Block, Tele Other (Lumbridge / Falador / Camelot). diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md index a6dc853fd5..8c9d91ff38 100644 --- a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md +++ b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md @@ -5,6 +5,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [2.2.34] + +### Fixed +- **Shipwreck highlight overlay**: Wreck lists are refreshed on the client each game tick and read by the overlay from that snapshot, so highlights draw reliably instead of depending on cache queries from the overlay render path. +- **Cargo hold + full inventory**: With **Use Cargo Hold** enabled, a full inventory that is only salvage is handled by depositing into the hold or entering hold processing; the script no longer tries to deploy the hook in that situation. + +### Added +- **Idle inventory cleanup**: When there is no salvage in your inventory but **Open Caskets**, **Drop Items**, or **Enable Alching** would change the inventory, the plugin can run one drop / casket / alch pass before salvaging so leftover loot does not sit in the way. + +### Changed +- **Cargo hold behaviour**: + - Opens the hold and uses **Deposit inventory** in the cargo UI (not the salvaging station) when banking salvage from a full inventory. + - Tracks **total occupied slots** and **salvage stacks** separately from the hold item grid so “full” and **processing** decisions match how the hold actually behaves (non-salvage items can occupy slots while salvage remains to process). + - Re-reads the grid periodically while the hold flow is active; when you are idle (no nearby wreck and no salvage in inventory), the script avoids opening the hold on a timer just to resync counts. + - Initialises the hold as soon as cargo-hold mode is active, before the “already salvaging” wait, so boarding or toggling the option is not blocked by animation checks. + - Resolves the hold game object using the player world view first, with a fallback lookup if needed. + +--- + +## [2.2.0] + +### Added +- **Use Cargo Hold** (config checkbox, default: off) + When enabled while salvaging, shipwreck salvage is deposited into your boat cargo hold via the hold interface (**Deposit inventory**) instead of using the salvaging station. Capacity and occupied slots are read from the cargo hold interface on first use; if the hold is full or would overflow the next deposit (using the same conservative rule as the design spec: zero free slots, or hold free slots below current empty inventory slots), the script enters a **processing** phase: it withdraws salvage from the hold in batches, closes the UI, and reuses the existing full-inventory routine (drop junk, open caskets, high alch) until salvage stacks in the hold reach zero, then resumes hook deployment. Manual hold interactions can desync the internal count; the script re-reads the interface when the hold object ID switches between no-cargo and cargo visuals. If the cargo UI fails to open repeatedly during processing, processing mode stops to avoid an infinite loop. + +--- + ## [2.1.0] ### Fixed diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md index 4cbf7cd70c..4c6f762fd4 100644 --- a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md +++ b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md @@ -7,7 +7,8 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. ### Salvaging - **Automatic Shipwreck Detection**: Finds and salvages nearby shipwrecks within a 15-tile radius - **Smart Inventory Management**: Checks inventory status before attempting to salvage — clears a full inventory before looking for new wrecks -- **Salvaging Station Support**: Deposits salvage at your boat's salvaging station (if installed) +- **Salvaging Station Support**: Deposits salvage at your boat's salvaging station (if installed), unless **Use Cargo Hold** is enabled +- **Cargo Hold (optional)**: Opens the hold and uses **Deposit inventory** in the cargo UI; tracks occupied slots and salvage stacks from the hold grid. When the hold is full or nearly full, withdraws and runs the same alch/drop/casket pipeline until salvage stacks in the hold are cleared. Avoids unnecessary hold open/close while idle away from wrecks without salvage - **Hook Deployment**: Automatically deploys your boat's salvaging hook on nearby shipwrecks ### Inventory Management @@ -22,6 +23,7 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. - **Inactive Wrecks**: Highlights depleted shipwrecks/stumps (gray by default) - **High Level Wrecks**: Highlights shipwrecks above your sailing level (red by default) - **Customizable Colors**: All highlight colors are fully customizable +- Wreck data for the overlay is updated on each game tick on the client thread so highlights stay in sync with the scene ## Configuration @@ -44,6 +46,12 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. - Caskets are opened after the first drop pass (to ensure space for loot) and before alching - Any junk from casket loot is caught by a second drop pass after alching +**Use Cargo Hold** (default: disabled) +- When enabled, you must be on your boat with a cargo hold in range. The script opens the hold to learn capacity, reads **occupied slots** and **salvage stacks** from the hold item grid, and when your inventory is full of salvage it uses **Deposit inventory** in the cargo UI (instead of the salvaging station). +- When the hold has no free slots, or its free slots are fewer than your current empty inventory slots, the script withdraws salvage from the hold and runs the same drop/casket/alch steps as a full inventory until **salvage stacks in the hold** reach zero, then continues salvaging (other items may still occupy slots). +- While you are idle with no nearby wreck and no salvage in inventory, the script does not keep opening the hold only to refresh counts. +- Turn this off to restore the original station-only behaviour. If the hold is not available (wrong place, no hold), salvaging waits until it can initialise the hold. + **Alch Order** (default: LIST_ORDER) - Controls the order in which matching items are alched across your inventory - `LIST_ORDER` — alches by item name order in your Alch Items list (original behaviour) @@ -80,14 +88,15 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. 1. **Inventory Check First**: On each tick, the plugin checks whether inventory is full before looking for wrecks 2. **If inventory is full**: - - If salvage items are present → deposit at salvaging station (or drop junk if no station) + - If salvage items are present → open cargo hold and **Deposit inventory** (**Use Cargo Hold**), else salvaging station (or drop junk if no station) - Otherwise: 1. Drop configured junk items 2. Open caskets (if enabled) — space has been made by the drop step 3. High alch configured items (if enabled) — includes any loot from opened caskets 4. Drop again — catches any junk that came from casket loot -3. **If inventory has space**: find the nearest wreck and deploy the salvaging hook -4. **Repeat** +3. **If inventory has space** and there is no salvage to protect: if **Open Caskets**, **Drop Items**, or **Enable Alching** would still change the inventory, the plugin may run one cleanup pass (same order as step 2) before looking for wrecks +4. **If inventory has space** after that: find the nearest wreck and deploy the salvaging hook +5. **Repeat** ## Shipwreck Types @@ -133,6 +142,15 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. ## Version History +**2.2.34** +- Reliable **shipwreck highlights** via per-tick client-side wreck snapshots for the overlay +- **Cargo hold**: **Deposit inventory** in the UI, separate **salvage stack** vs **slot** tracking, smarter resync (no idle spam), earlier hold initialisation, improved hold object lookup +- **Full inventory + cargo hold**: full salvage inventories deposit or enter hold processing instead of deploying the hook +- **Idle inventory cleanup** when there is no salvage but casket/drop/alch work remains + +**2.2.0** +- Optional **Use Cargo Hold** mode for salvaging: deposit salvage into the boat hold, process the hold when full or nearly full, then resume hooks + **2.1.0** - Fixed alch loop only alching one of each item instead of exhausting all stacks - Fixed inventory check now happens before wreck detection diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md new file mode 100644 index 0000000000..9904ee9939 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md @@ -0,0 +1,35 @@ +# Sisyphus: Infernal Pact + +Automates the **Yama's Lair stepping-stone circuit** in the Demonic Pacts League. + +## What it does + +- Navigates the full stepping-stone route through Yama's Lair +- **Never clicks a tile with fire on it** (checks NPC ID 15608 with a 2-tile exclusion radius) +- Loops the central stone circuit continuously until the configured stone count is reached +- Uses rapid-fire 200–400 ms click intervals for maximum efficiency +- Prevents backtracking by tracking the last 3 visited stones + +## Setup + +1. Log into your account and travel to Yama's Lair +2. Stand near the first entry stone at `(1486, 5598)` +3. Enable the plugin in the Microbot plugin list +4. Set **Stones to Step** to however many you need (default 666 for the Demonic Pacts task) +5. Click **Start** — the bot handles the rest + +## Settings + +| Setting | Default | Description | +|---|---|---| +| Stones to Step | 666 | Total safe stone hops before the plugin stops | + +## Notes + +- The plugin stops automatically once the configured stone count is reached +- Fire NPCs move — the bot re-checks every tick so it will never step on an active fire tile +- Works on a tight 200–400 ms scheduler (effectively 1-tick clicking) + +## Author + +Sisyphus diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 0bbce8830b..debcae7bac 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -5,11 +5,16 @@ import java.util.stream.Collectors; import net.runelite.client.plugins.fishing.FishingPlugin; +import net.runelite.client.plugins.microbot.GiantSeaweedFarmer.GiantSeaweedFarmerPlugin; import net.runelite.client.plugins.microbot.agentserver.AgentServerPlugin; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.astralrc.AstralRunesPlugin; +import net.runelite.client.plugins.microbot.birdhouseruns.FornBirdhouseRunsPlugin; import net.runelite.client.plugins.microbot.autofishing.AutoFishingPlugin; +import net.runelite.client.plugins.microbot.crafting.jewelry.JewelryPlugin; import net.runelite.client.plugins.microbot.example.ExamplePlugin; +import net.runelite.client.plugins.microbot.kraken.KrakenPlugin; +import net.runelite.client.plugins.microbot.leftclickcast.LeftClickCastPlugin; import net.runelite.client.plugins.microbot.sailing.MSailingPlugin; import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -20,7 +25,9 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, - AgentServerPlugin.class + AgentServerPlugin.class, + FornBirdhouseRunsPlugin.class, + GiantSeaweedFarmerPlugin.class }; public static void main(String[] args) throws Exception