From e575f00111ccb8e1eced19871ed9d64aba59b79c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 00:15:01 +0000 Subject: [PATCH] Improve castSpellOnNpc API: accept spell names instead of raw IDs Addresses #24 - the castSpellOnNpc API was confusing because it required opaque numeric spell component IDs with no guidance on valid values. Changes: - Add sdk/spells.ts as the canonical source for spell constants, exported as `Spells` with a `SpellName` type derived from its keys - Update castSpellOnNpc, sendSpellOnNpc, and sendSpellOnItem to accept `SpellName | number` (e.g. 'WIND_STRIKE' or 1152), following the same pattern as sendTogglePrayer which accepts PrayerName | number - Re-export Spells and SpellName from actions.ts for convenient imports - Replace inline Spells definition in save-generator.ts with re-export - Remove duplicate Spells constant from scripts/magic/script.ts - Document all spell IDs in API.md with a new Constants section Closes #24 https://claude.ai/code/session_01CjzdFWTHRru5kYDGeXgyZe --- scripts/magic/script.ts | 8 +--- sdk/API.md | 59 ++++++++++++++++++++++++-- sdk/actions.ts | 11 ++++- sdk/index.ts | 17 ++++++-- sdk/spells.ts | 71 ++++++++++++++++++++++++++++++++ sdk/test/utils/save-generator.ts | 38 +---------------- 6 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 sdk/spells.ts diff --git a/scripts/magic/script.ts b/scripts/magic/script.ts index 4ffb015b4..9e33d7b69 100644 --- a/scripts/magic/script.ts +++ b/scripts/magic/script.ts @@ -20,13 +20,7 @@ import { runScript, type ScriptContext } from '../../sdk/runner'; import { generateSave, TestPresets } from '../../sdk/test/utils/save-generator'; import { launchBotWithSDK } from '../../sdk/test/utils/browser'; import type { NearbyNpc } from '../../sdk/types'; - -// Spell component IDs -const Spells = { - WIND_STRIKE: 1152, - WATER_STRIKE: 1154, - EARTH_STRIKE: 1156, -}; +import { Spells } from '../../sdk/spells'; // Locations const CHICKEN_COOP = { x: 3235, z: 3295 }; // Near Lumbridge diff --git a/sdk/API.md b/sdk/API.md index 20d33dfe3..a1f76054c 100644 --- a/sdk/API.md +++ b/sdk/API.md @@ -29,7 +29,7 @@ These methods wait for the **effect to complete**, not just server acknowledgmen | `findEquippedItem(pattern)` | Find an equipped item by name pattern. | | `eatFood(target)` | Eat food to restore hitpoints. | | `attackNpc(target, timeout)` | Attack an NPC, walking to it if needed. | -| `castSpellOnNpc(target, spellComponent, timeout)` | Cast a combat spell on an NPC. | +| `castSpellOnNpc(target, spell, timeout)` | Cast a combat spell on an NPC. `spell` accepts a `SpellName` (e.g. `'WIND_STRIKE'`) or numeric component ID. | | `craftLeather(product?)` | Craft leather into armour using needle and thread. | ### Woodcutting & Firemaking @@ -158,8 +158,8 @@ These methods resolve when server **acknowledges** them (not when effects comple | `sendShopSell(slot, amount)` | Sell to shop by slot and amount. | | `sendSetCombatStyle(style)` | Set combat style (0-3). | | `sendTogglePrayer(prayer)` | Toggle a prayer on or off by name or index (0-14). | -| `sendSpellOnNpc(npcIndex, spellComponent)` | Cast spell on NPC using spell component ID. | -| `sendSpellOnItem(slot, spellComponent)` | Cast spell on inventory item. | +| `sendSpellOnNpc(npcIndex, spell)` | Cast spell on NPC. Accepts `SpellName` or numeric component ID. | +| `sendSpellOnItem(slot, spell)` | Cast spell on inventory item. Accepts `SpellName` or numeric component ID. | | `sendSetTab(tabIndex)` | Switch to a UI tab by index. | | `sendSay(message)` | Send a chat message. | | `sendWait(ticks)` | Wait for specified number of game ticks. | @@ -192,6 +192,59 @@ These methods resolve when server **acknowledges** them (not when effects comple --- +## Constants + +### Spells + +Spell constants for use with `castSpellOnNpc`, `sendSpellOnNpc`, and `sendSpellOnItem`. + +```typescript +import { Spells } from './actions'; + +// Use by name +await bot.castSpellOnNpc(target, 'WIND_STRIKE'); + +// Or use the Spells constant +await bot.castSpellOnNpc(target, Spells.WIND_STRIKE); +``` + +| Name | ID | Notes | +|------|----|-------| +| `WIND_STRIKE` | 1152 | Level 1 combat spell | +| `CONFUSE` | 1153 | | +| `WATER_STRIKE` | 1154 | Level 5 combat spell | +| `ENCHANT_LVL1` | 1155 | Sapphire | +| `EARTH_STRIKE` | 1156 | Level 9 combat spell | +| `WEAKEN` | 1157 | | +| `FIRE_STRIKE` | 1158 | Level 13 combat spell | +| `WIND_BOLT` | 1160 | | +| `CURSE` | 1161 | | +| `LOW_ALCHEMY` | 1162 | | +| `WATER_BOLT` | 1163 | | +| `VARROCK_TELEPORT` | 1164 | | +| `ENCHANT_LVL2` | 1165 | Emerald | +| `EARTH_BOLT` | 1166 | | +| `LUMBRIDGE_TELEPORT` | 1167 | | +| `FIRE_BOLT` | 1169 | | +| `FALADOR_TELEPORT` | 1170 | | +| `WIND_BLAST` | 1172 | | +| `SUPERHEAT` | 1173 | | +| `CAMELOT_TELEPORT` | 1174 | | +| `WATER_BLAST` | 1175 | | +| `ENCHANT_LVL3` | 1176 | Ruby | +| `EARTH_BLAST` | 1177 | | +| `HIGH_ALCHEMY` | 1178 | | +| `ENCHANT_LVL4` | 1180 | Diamond | +| `FIRE_BLAST` | 1181 | | +| `WIND_WAVE` | 1183 | | +| `WATER_WAVE` | 1185 | | +| `ENCHANT_LVL5` | 1187 | Dragonstone | +| `EARTH_WAVE` | 1188 | | +| `FIRE_WAVE` | 1189 | | +| `BIND` | 1572 | | + +--- + ## Result Types ### PlayerCombatState diff --git a/sdk/actions.ts b/sdk/actions.ts index 07741ce5d..413f9a0c1 100644 --- a/sdk/actions.ts +++ b/sdk/actions.ts @@ -41,6 +41,7 @@ import type { PrayerName } from './types'; import { PRAYER_INDICES, PRAYER_NAMES, PRAYER_LEVELS } from './types'; +import { resolveSpell, type SpellName } from './spells'; export class BotActions { private helpers: ActionHelpers; @@ -1477,8 +1478,13 @@ export class BotActions { } } - /** Cast a combat spell on an NPC. */ - async castSpellOnNpc(target: NearbyNpc | string | RegExp, spellComponent: number, timeout: number = 3000): Promise { + /** Cast a combat spell on an NPC. Accepts a spell name (e.g. 'WIND_STRIKE') or numeric component ID. */ + async castSpellOnNpc(target: NearbyNpc | string | RegExp, spell: SpellName | number, timeout: number = 3000): Promise { + const spellComponent = resolveSpell(spell); + if (spellComponent === undefined) { + return { success: false, message: `Unknown spell: ${spell}` }; + } + const npc = this.helpers.resolveNpc(target); if (!npc) { return { success: false, message: `NPC not found: ${target}`, reason: 'npc_not_found' }; @@ -2600,3 +2606,4 @@ export class BotActions { // Re-export for convenience export { BotSDK } from './index'; export * from './types'; +export { Spells, type SpellName } from './spells'; diff --git a/sdk/index.ts b/sdk/index.ts index 9eb22cb07..d97a8d85b 100644 --- a/sdk/index.ts +++ b/sdk/index.ts @@ -21,6 +21,7 @@ import type { PrayerName } from './types'; import { PRAYER_INDICES, PRAYER_NAMES } from './types'; +import { resolveSpell, type SpellName } from './spells'; import * as pathfinding from './pathfinding'; interface SyncToSDKMessage { @@ -913,13 +914,21 @@ export class BotSDK { .filter((name): name is PrayerName => name !== null); } - /** Cast spell on NPC using spell component ID. */ - async sendSpellOnNpc(npcIndex: number, spellComponent: number): Promise { + /** Cast spell on NPC. Accepts a spell name (e.g. 'WIND_STRIKE') or numeric component ID. */ + async sendSpellOnNpc(npcIndex: number, spell: SpellName | number): Promise { + const spellComponent = resolveSpell(spell); + if (spellComponent === undefined) { + return { success: false, message: `Unknown spell: ${spell}` }; + } return this.sendAction({ type: 'spellOnNpc', npcIndex, spellComponent, reason: 'SDK' }); } - /** Cast spell on inventory item. */ - async sendSpellOnItem(slot: number, spellComponent: number): Promise { + /** Cast spell on inventory item. Accepts a spell name (e.g. 'LOW_ALCHEMY') or numeric component ID. */ + async sendSpellOnItem(slot: number, spell: SpellName | number): Promise { + const spellComponent = resolveSpell(spell); + if (spellComponent === undefined) { + return { success: false, message: `Unknown spell: ${spell}` }; + } return this.sendAction({ type: 'spellOnItem', slot, spellComponent, reason: 'SDK' }); } diff --git a/sdk/spells.ts b/sdk/spells.ts new file mode 100644 index 000000000..45745a4c3 --- /dev/null +++ b/sdk/spells.ts @@ -0,0 +1,71 @@ +// Spell component IDs (from content/pack/interface.pack) +// These map to the interface component ID used by the game protocol. + +export const Spells = { + // Strike spells (lowest tier combat) + WIND_STRIKE: 1152, + CONFUSE: 1153, + WATER_STRIKE: 1154, + ENCHANT_LVL1: 1155, // Sapphire + EARTH_STRIKE: 1156, + WEAKEN: 1157, + FIRE_STRIKE: 1158, + + // Bolt spells + WIND_BOLT: 1160, + CURSE: 1161, + LOW_ALCHEMY: 1162, + WATER_BOLT: 1163, + VARROCK_TELEPORT: 1164, + ENCHANT_LVL2: 1165, // Emerald + EARTH_BOLT: 1166, + LUMBRIDGE_TELEPORT: 1167, + FIRE_BOLT: 1169, + FALADOR_TELEPORT: 1170, + + // Blast spells + WIND_BLAST: 1172, + SUPERHEAT: 1173, + CAMELOT_TELEPORT: 1174, + WATER_BLAST: 1175, + ENCHANT_LVL3: 1176, // Ruby + EARTH_BLAST: 1177, + HIGH_ALCHEMY: 1178, + ENCHANT_LVL4: 1180, // Diamond + FIRE_BLAST: 1181, + + // Wave spells (highest tier) + WIND_WAVE: 1183, + WATER_WAVE: 1185, + ENCHANT_LVL5: 1187, // Dragonstone + EARTH_WAVE: 1188, + FIRE_WAVE: 1189, + + // Other + BIND: 1572, +} as const; + +/** The name of a spell (e.g. 'WIND_STRIKE', 'FIRE_BOLT'). */ +export type SpellName = keyof typeof Spells; + +// Reverse lookup: component ID → spell name (for validation/error messages) +const spellById = new Map( + (Object.entries(Spells) as [SpellName, number][]).map(([name, id]) => [id, name]) +); + +/** + * Resolve a spell name or component ID to a numeric component ID. + * Returns undefined if the spell name is not recognized. + */ +export function resolveSpell(spell: SpellName | number): number | undefined { + if (typeof spell === 'number') { + return spell; + } + const id = Spells[spell]; + return id; +} + +/** Get the spell name for a component ID, if known. */ +export function getSpellName(componentId: number): SpellName | undefined { + return spellById.get(componentId); +} diff --git a/sdk/test/utils/save-generator.ts b/sdk/test/utils/save-generator.ts index 166d43712..210989a31 100644 --- a/sdk/test/utils/save-generator.ts +++ b/sdk/test/utils/save-generator.ts @@ -108,42 +108,8 @@ export const Items = { BIG_BONES: 532, }; -// Spell component IDs (from content/pack/interface.pack) -export const Spells = { - // Combat spells - WIND_STRIKE: 1152, - CONFUSE: 1153, - WATER_STRIKE: 1154, - ENCHANT_LVL1: 1155, // Sapphire - EARTH_STRIKE: 1156, - WEAKEN: 1157, - FIRE_STRIKE: 1158, - WIND_BOLT: 1160, - CURSE: 1161, - LOW_ALCHEMY: 1162, - WATER_BOLT: 1163, - VARROCK_TELEPORT: 1164, - ENCHANT_LVL2: 1165, // Emerald - EARTH_BOLT: 1166, - LUMBRIDGE_TELEPORT: 1167, - FIRE_BOLT: 1169, - FALADOR_TELEPORT: 1170, - WIND_BLAST: 1172, - SUPERHEAT: 1173, - CAMELOT_TELEPORT: 1174, - WATER_BLAST: 1175, - ENCHANT_LVL3: 1176, // Ruby - EARTH_BLAST: 1177, - HIGH_ALCHEMY: 1178, - ENCHANT_LVL4: 1180, // Diamond - FIRE_BLAST: 1181, - WIND_WAVE: 1183, - WATER_WAVE: 1185, - ENCHANT_LVL5: 1187, // Dragonstone - EARTH_WAVE: 1188, - FIRE_WAVE: 1189, - BIND: 1572, -}; +// Re-export spell constants from the canonical source +export { Spells } from '../../spells'; // Skill indices export const Skills = {