feat(sdk): filter findNearbyLoc/interactLoc by loc id (#23)#40
Open
Lawson-Darrow wants to merge 2060 commits into
Open
feat(sdk): filter findNearbyLoc/interactLoc by loc id (#23)#40Lawson-Darrow wants to merge 2060 commits into
Lawson-Darrow wants to merge 2060 commits into
Conversation
- Add bot_arcs/**/*.ts and scripts/**/*.ts to tsconfig include - Fix state.combat -> state.player?.combat (combat on PlayerState) - Fix shop.items -> shop.shopItems (correct property name) - Fix player.hp/maxHp -> use Hitpoints skill level/baseLevel - Fix player.isAnimating -> check animId !== -1 - Fix sendClickItem/sendClickInventory -> sendUseItem - Fix sendInterfaceButton -> sendClickInterfaceComponent - Fix bot.eat -> bot.eatFood - Add null checks for array indexed access (noUncheckedIndexedAccess) - Fix type-only import for ScriptContext (verbatimModuleSyntax) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When the MCP server is launched from a different cwd (e.g. /workspace
in Docker), it can't find bots/{name}/bot.env. Fall back to resolving
relative to the mcp/ directory (one level up = repo root).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SDK clients could override the username field in sdk_action and sdk_screenshot_request messages to control other users' bots after authenticating as their own. Pin all post-auth actions to the session's authenticated targetUsername. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
navigateDialog now logs available options and skips instead of silently defaulting to index 0. Formatter displays "HP" instead of "Hitpoints". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The engine TCP login normalizes the username via toSafeName before the DB lookup, but the gateway/SDK auth path forwarded the raw value. A URL param like ?bot=Bitty would hit the sdk_auth handler with "Bitty" and fail the case-sensitive account lookup, even when the engine login with the same credentials succeeded as "bitty". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates all three vendored upstream repos (Engine-TS, Content, Client-TS) to the 274 game snapshot with all rs-sdk modifications re-applied via a 3-way merge (254 base + local mods, replayed onto upstream 274). ## Engine - Bump @2004scape/rsbuf 254.1.0 -> 274.1.0 (keep rsmod-pathfinder fork pin) - Adapt to 274's nested WorldConfig: Environment.ts now exposes flat back-compat aliases (WEB_PORT, NODE_ID, ...) plus the rs-sdk-only keys the bot framework uses, and createDefaultWorldConfig carries our defaults (xpRate 25, web.port 8888) - Collision export (web/pages/api.ts) reads 274's in-engine TS routefinder instead of the dropped @2004scape/rsmod-pathfinder WASM, which the engine no longer feeds - Remove dead GameMap.findLongPath (274 routefinder has no long-path variant; the SDK does long-distance pathfinding client-side) - Re-apply security/login mods (sdk_auth, IP login tracking, login-takeover, banned usernames, no dev staffmodlevel on non-prod) onto 274's auth refactor - Add MESSAGE_PUBLIC server packet at the free opcode 255 (254's 81 is now OBJ_ADD) - NODE_WS_ONDEMAND now defaults on: the 274 web client streams on-demand assets over the game WebSocket and otherwise stalls at ~60% on load ## Content - Full 254->274 content update with our 4 script/config patches re-applied - worldmap.jag regenerated for /mapview from the 274 maps (via engine build) ## Webclient - Rebuild standard + bot + viewer bundles; CLIENT_VERSION 274 - ServerProt: keep 274 opcodes, add MESSAGE_PUBLIC=255 with -1 size table entry - Replace removed Packet.pIsaac() calls with 274's p1Enc() - Disable terser property mangling (274 enabled it): the bot framework accesses client members by name from bot.ejs / the Puppeteer harness ## SDK - Regenerate sdk/collision-data.json from 274 maps (905k -> 958k tiles) ## start.ts - Add 274 to the version menu Verified end-to-end: engine boots and loads 274 maps, content packs, engine typechecks clean, webclient builds, a headless bot logs in, sdk.getState() returns a complete snapshot, and bot.walkTo() pathfinds/moves on the regenerated collision data. Map and hiscores sprites serve correctly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
On 274, accepting the character design left the design interface (player_kit, 3559) open forever, so skipTutorial got stuck before reaching the tutorial dialogs. The design "Accept" button is a normal button (clientcode=326 / CC_ACCEPT_DESIGN). A real click sends BOTH the IDK_SAVEDESIGN packet (which only sets the appearance) AND an IF_BUTTON for the component. The IF_BUTTON is what fires the content trigger [if_button,player_kit:accept] -> if_close -> [if_close,player_kit] -> queue(tutorial_designed_character), which closes the interface and advances the tutorial. The bot's acceptCharacterDesign() only sent IDK_SAVEDESIGN, so the engine saved the appearance (verified: handler accepts it) but nothing closed the modal. acceptCharacterDesign() now also sends the IF_BUTTON for the CC_ACCEPT_DESIGN component (found via IfType.list), matching a real click. Verified: the design modal closes (interface 3559 -> -1), the tutorial advances to the guide dialog, and waitForTicks/clickDialog proceed normally. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 254->274 merge dropped the rs-sdk bot-bridge updates to renamed Client members, leaving them referencing fields that are now undefined: - StateCollector/BotOverlay: chatComId -> chatModalId. With chatComId undefined, `undefined !== -1` made dialog state ALWAYS report isOpen=true with zero options, so skipTutorial / navigateDialog / any dialog click hung forever (firing RESUME_PAUSEBUTTON into nothing). - Client.setTab/setCombatStyle: sideOverlayId -> sideIcon, sideTab -> activeIcon, redrawSidebar -> redrawSide, redrawSideicons -> redrawIcons. Verified end-to-end: bot.skipTutorial() returns "Tutorial skipped after 3 dialog clicks", teleports to Lumbridge with the starter kit. Note: esbuild bundles despite TS errors, so these merge misses shipped silently -- `bunx tsc --noEmit` in server/webclient surfaces them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- MapView: when the visible area is larger than the map (zoomed out), the min/max focus clamps conflicted and pinned focus to a fixed value, freezing panning. Center the axis in that case; otherwise clamp to a single edge. - ItemViewer: correct import casing Jagfile -> JagFile (matches #/io/JagFile.js). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
274 reads nested config only from data/config/world.json and ignores process.env, so fly.io [env] vars (WEB_PORT, LOGIN_SERVER, NODE_MEMBERS, ...) were dropped at runtime. Overlay process.env on top of the loaded config in Environment.ts (env wins; absent keys fall back to world.json/defaults; local dev with no such vars is a no-op). Export migrateFromLegacyEnv to reuse its existing flat-env -> nested-config mapping. Also drop the Dockerfile's `cp .env.example .env` build step: 274 removed .env.example, and with shell precedence its failure aborted the engine build. No .env file is needed now that config comes from process.env. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nder) 274's clientRoutefinder mode makes the server expect the *client* to send the movement path to an interaction target; it no longer pathfinds to locs itself. The bot's Client.interactLoc() sent only the OPLOC packet with no movement, so the player never walked and the server replied "I can't reach that!" whenever it wasn't already adjacent. This broke every bot.* action that walks-then- interacts with a loc (chopTree, mine, openDoor, openBank-from-afar, etc.). Mirror the existing useItemOnLoc(): convert world->scene coords and call tryMove(...type=2) with the loc footprint before writing OPLOC, so the client routefinder emits the path. Verified locally (clientRoutefinder=true): a bot 4 tiles from a tree now auto-walks adjacent and chops it (+625 wc xp), where it previously failed with "I can't reach that!". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The webclient bundle (server/webclient/out/) is gitignored and the Dockerfile did not build it, so `fly deploy` silently shipped whatever was in the local out/ dir — easy to deploy a stale client. Add a webclient dep-install + build step (mirroring engine/gateway), and exclude server/webclient/out and node_modules from the Docker context so the in-image build is authoritative. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…finder) Same regression class as the interactLoc fix: in 274 clientRoutefinder mode the server expects the client to send the route to an interaction target. talkToNpc, interactNpc and interactPlayer wrote only their op packet with no movement, so the player never walked and the server replied "I can't reach that!" unless already adjacent. This broke bot.talkTo / interactNpc / attackNpc / pickpocket / interactPlayer (trade/follow) from any distance. Add a tryMove(...type=2) to each, targeting the npc/player routeX/routeZ with a 1x1 footprint, mirroring spellOnNpc/useItemOnNpc. Audited all interaction methods in Client.ts; these three were the only ones still missing the walk-to step. Verified locally + unit tests pass: a bot now walks to a distant NPC and engages. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GameMap.init loads map collision from the packed zip (data/pack/.cache/maps-server.zip) when present, falling back to the raw data/pack/server/maps/ dir only if it's absent. The collision export discovered mapsquares from the raw dir alone — but the zip and dir don't fully overlap: the zip had ~41 mapsquares the dir lacked (the SW mainland around Yanille, the Tirannwn band z3584-3775, several high-z instanced areas). Those were loaded and allocated by the engine but silently dropped from the export, so the SDK's collision-data.json had no data there and bots got stranded with "no path". Union the zip and dir mapsquare names for discovery; the existing isZoneAllocated check still filters to mapsquares actually loaded into the routefinder. Raises export coverage from 442 to ~483 mapsquares. (collision-data.json regenerated from this in a follow-up commit.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ENOENT) The previous commit added zip-only mapsquares to discovery, but the door scanner still loaded m/l packets via Packet.load() from the raw maps dir, which throws ENOENT for squares present only in the zip (e.g. m39_71). Add a loadMapPacket() helper that reads from the dir or falls back to the unzipped entries, and skip squares with no loc data. Verified the zip carries all 483 m+l entries. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Regenerated from the fixed /api/exportCollision (members prod). Adds the 41 mapsquares the old dir-only discovery dropped: the SW mainland around Yanille (~x2048-2367, z3072-3391), the Tirannwn band (z3584-3775), and several high-z instanced squares. zones 113k -> 123.6k, tiles 958k -> 1.07M, doors 2550 -> 2603. Bots in those regions previously got "no path / zone not allocated" and were stranded; the SDK pathfinder now loads and routes there. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Flip the default so newly-created bots show other players' public chat (SHOW_CHAT=true) instead of opting in. The controller code (sdk/runner.ts, mcp/api) already defaulted to true, but the bot.env template wrote SHOW_CHAT=false into every new bot and the docs said off-by-default; this makes the default consistent. Template -> SHOW_CHAT=true, create-bot.ts defaults to chat-on with a --no-chat/--hide-chat opt-out (--show-chat kept as a no-op for back-compat), sdk default fallback false -> true, README updated. Existing bots keep their explicit SHOW_CHAT=false until changed individually. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The webclient's levelExperience table used the wrong XP formula (exponent /7.0, no *10 multiplier), so it computed much higher XP thresholds per level than the engine. Loaded saves that were level 99 on the server displayed as lower levels in the client. Align the client table generation with engine/src/engine/entity/Player.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
server/engine/public/client/{client.js,ondemandworker.js} were tracked
copies of webclient build artifacts. handlePublicFiles() (web/pages/static.ts)
serves public/<path> BEFORE falling back to webclient/out, so these stale
copies shadowed the freshly-built /client/client.js on every deploy —
which is why the XP-curve fix (0f29234) had no effect on prod.
These are leftovers from the pre-'build-in-image' copy-step deploy model.
The engine now serves the fresh out/standard build directly. Kept the genuine
non-built assets (SCC1_Florestan.sf2 soundfont, tinymidipcm.wasm).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The client was imported as a static ES module URL ('./bot/client.js')
with no version query, and the engine sent no cache headers, so browsers
held the stale cached client indefinitely — even through hard refresh.
After the XP-curve fix, the corrected bundle was live on the origin but
players still saw old skill levels because their browser never refetched.
- Append ?v=<CLIENT_CACHEBUST> (per-process-start token) to the client.js
import in bot.ejs and client.ejs, so each deploy yields a new module URL.
- Send Cache-Control: no-cache on the HTML pages so the browser always
revalidates and picks up the new ?v= token.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous fix (0f29234) correctly switched the client curve to /10 to match the engine, but also wrongly added the engine's *10 multiplier. The engine stores XP in x10 'fine' units (its table has *10), but the server sends the client REAL xp (divided by 10). So the client table must be in real-xp units WITHOUT *10 (as upstream had it) — only the curve shape (/10) needed to match. With the erroneous *10, client base-level thresholds were 10x too large, so skills whose real xp cleared 99 under the engine curve (e.g. Prayer 1,071,075) showed as baseLevel 66-90. Verified against live getState: all 19 skills now compute to baseLevel 99 (L99 real-xp threshold 998,458). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
findNearbyLoc and scanFindNearbyLoc matched only on name, so same-named locations (e.g. ore rocks all named "Rocks") could not be disambiguated even though NearbyLoc already carries an id. Add an optional id filter via a new LocFilter type and a shared locMatcher predicate, with backward-compatible overloads. interactLoc accepts a LocFilter and re-binds to the same variant after walking. Adds unit tests for the matching logic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #23.
findNearbyLocandscanFindNearbyLoconly matched on the location name, so same-named objects (all mining rocks are "Rocks", each ore has a distinct id) couldn't be disambiguated, even thoughNearbyLocalready carries anid.This adds an optional id filter:
LocFilter { name?, id? }type and a sharedlocMatcherpredicate both finders use.findNearbyLoc(/rocks/i)still works;findNearbyLoc(/rocks/i, { id: 2091 })andfindNearbyLoc({ id: 2091 })are new. Same forscanFindNearbyLoc(the legacy numeric radius arg still works).interactLocaccepts aLocFilterand re-binds to the same variant by id after walking instead of the nearest same-named object.One behavior note: when
interactLocis given a resolvedNearbyLoc, the post-walk re-find now also pins its id (previously name-only). This is more precise and is the intended fix here.Added unit tests for
locMatcher(8 cases).bun testpasses; no new type errors in changed files.