Files
2026-07-26 15:16:37 -04:00

6.3 KiB

Combat

Successful patterns for combat training.

Attacking NPCs

Use bot.attack() for cleaner code, or raw SDK for more control:

// Porcelain method (recommended)
const attack = await bot.attack(/cow/i);
if (!attack.success) console.warn(attack.reason, attack.message);

// Raw SDK dispatch (observe combat state afterwards)
const state = sdk.getState();
const npc = state?.nearbyNpcs.find(n => /cow/i.test(n.name));
const attackOpt = npc?.optionsWithIndex.find(o => /attack/i.test(o.text));
if (npc && attackOpt) {
    await sdk.sendInteractNpc(npc.index, attackOpt.opIndex);
    await sdk.waitForCondition(s => s.player?.combat.inCombat === true, 5_000);
}

Combat Style Cycling

Combat-style indices depend on the equipped weapon the SDK reads the table from the combat tab the server installed, so resolve indices from state

const targetSkill = 'Defence';
// A controlled style lists every skill it trains, so match against trainsSkills.
const style = sdk.getState()?.combatStyle?.styles.find(
    candidate => candidate.trainsSkills.includes(targetSkill),
);
if (!style) {
    throw new Error(`No ${targetSkill} style is available for the equipped weapon`);
}
await sdk.sendSetCombatStyle(style.index);
await sdk.waitForCondition(
    state => state.combatStyle?.currentStyle === style.index,
    5_000,
);

Checking Combat State

const state = sdk.getState();
const inCombat = state?.player?.combat.inCombat ?? false;

// Animation is a useful heuristic, but is not specific to combat.
const isAnimating = (state?.player?.animId ?? -1) !== -1;

Safe Training Locations

Location Coordinates Targets Notes
Lumbridge cows (3253, 3290) Cows Safe, good for all levels. South gate at (3253, 3266)
Lumbridge goblins (3252, 3230) Goblins, rats Mixed enemies. Cluster runs NE from here — nothing at the old (3240, 3220) waypoint
Lumbridge chickens (3237, 3295) Chickens Very safe, feathers drop
Al Kharid warriors ~(3293, 3170) Al-Kharid warrior (lvl 9) Faster XP, kebabs nearby for food, can hit hard via multicombat vs low combat levels.

Cow Field Details (Proven from 200+ kills)

The cow field is fenced with a gate on the south side:

  • Field center: ~(3253, 3290)
  • South gate position: (3253, 3266)
  • Inside cow pen: x between 3242-3265, z between 3255-3298
function isInsideCowPen(x, z) {
    return x >= 3242 && x <= 3265 && z >= 3255 && z <= 3298;
}

Opening Gates

Cow field and chicken coop have fenced gates:

// Check for gate blocking path
const gate = sdk.getState()?.nearbyLocs.find(l => /gate/i.test(l.name));
if (gate) {
    const openOpt = gate.optionsWithIndex.find(o => /^open$/i.test(o.text));
    if (openOpt) {
        await bot.openDoor(gate);
    }
}

Finding New Targets

After killing an NPC, find the next one quickly:

async function findTarget(pattern) {
    const state = sdk.getState();
    if (!state) return null;

    return state.nearbyNpcs
        .filter(n => pattern.test(n.name))
        .filter(n => n.optionsWithIndex.some(o => /attack/i.test(o.text)))
        .sort((a, b) => a.distance - b.distance)[0] ?? null;
}

Looting Ground Items

CRITICAL: Use sdk.scanGroundItems() NOT state.nearbyLocs for dropped items!

// WRONG - nearbyLocs is for static objects (trees, rocks, etc.)
const wrongLoot = sdk.getState()?.nearbyLocs.filter(i => /hide/i.test(i.name));  // Won't work!

// CORRECT - scanGroundItems() for drops
const groundItems = await sdk.scanGroundItems();
const loot = groundItems.filter(i => /hide|bones|coins/i.test(i.name));

Limit Pickups Per Loop

Pick up a few items (e.g. 3), then return to combat. Prevents getting stuck in infinite loot loops:

const MAX_PICKUPS = 3;
const groundItems = await sdk.scanGroundItems();
const loot = groundItems
    .filter(i => /hide|bones/i.test(i.name))
    .filter(i => i.distance < 5)
    .slice(0, MAX_PICKUPS);

for (const item of loot) {
    await bot.pickupItem(item);
    await new Promise(r => setTimeout(r, 500));
}
// Back to combat

Error Handling for Long Runs (Critical!)

Timeouts and errors are frequent in crowded areas. Wrap attacks in try/catch:

// This pattern enabled consistent 10-minute runs
try {
    await bot.attack(/cow/i);
} catch (err) {
    console.log(`Attack timed out, trying next cow`);
    continue;  // Don't crash - just find another target
}

Common Messages and Handling

Message Meaning Response
"I'm already under attack" Crowded area, NPC in combat Find different target
"I can't reach that!" Obstacle or fence Move closer, check gates
Attack timeout Target died/moved Try next NPC

State Validation

Browser glitches sometimes return invalid positions. Validate state before acting:

const player = sdk.getState()?.player;
if (!player || player.worldX === 0 || player.worldZ === 0) {
    console.log('Invalid state - waiting for sync');
    await new Promise(r => setTimeout(r, 2000));
    continue;
}

// Also catch impossible position changes (>500 tiles = glitch)
if (Math.abs(player.worldX - lastX) > 500) {
    console.log('Position glitch detected, skipping action');
    continue;
}

Auto-Train Lowest Combat Stat

For balanced progression, automatically train whichever stat is lowest:

function getLowestCombatStat(state) {
    const skills = state.skills;
    const atk = skills.find(s => s.name === 'Attack')?.baseLevel ?? 1;
    const str = skills.find(s => s.name === 'Strength')?.baseLevel ?? 1;
    const def = skills.find(s => s.name === 'Defence')?.baseLevel ?? 1;

    if (def <= atk && def <= str) return 'Defence';
    if (str <= atk) return 'Strength';
    return 'Attack';
}

const state = sdk.getState();
if (!state) throw new Error('No world state');
const stat = getLowestCombatStat(state);
const style = state.combatStyle?.styles.find(option => option.trainsSkills.includes(stat));
if (!style) throw new Error(`No style trains ${stat} with the equipped weapon`);
await sdk.sendSetCombatStyle(style.index);
console.log(`Training ${stat} with ${style.name}`);

This pattern enabled balanced 60+ in all melee stats.