kill-switch-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults/actions/fight-loop.ts +117 -0
- package/defaults/actions/kite.ts +103 -0
- package/defaults/actions/loot-and-equip.ts +95 -0
- package/dist/server.js +18181 -0
- package/package.json +22 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fight the nearest player (or a specific target) with tick-optimized auto-eat.
|
|
3
|
+
*
|
|
4
|
+
* Uses RS PvP combat fundamentals:
|
|
5
|
+
* - Attack FIRST, eat AFTER — eating delays your attack by 3 ticks,
|
|
6
|
+
* but if you eat right after your hit lands, the delay expires before
|
|
7
|
+
* your next attack is ready. Zero DPS loss.
|
|
8
|
+
* - Emergency eat at HP <= max hit (13) regardless of threshold
|
|
9
|
+
* - Won't eat above HP 28 (40 max - 12 lobster heal = wasted healing)
|
|
10
|
+
* - Targets lowest-HP visible player for fastest kills (unless overridden)
|
|
11
|
+
*
|
|
12
|
+
* @param opts.eatAt - HP threshold to eat at (default: 25)
|
|
13
|
+
* @param opts.duration - How long to fight in ms (default: 15000)
|
|
14
|
+
* @param opts.target - Specific player name or /pattern/ (default: weakest nearby)
|
|
15
|
+
* @param opts.fleeAt - Food count to flee at if outnumbered (default: 3)
|
|
16
|
+
* @returns { hp, maxHp, foodLeft, target, nearbyPlayers } — status at end of loop
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* await actions.fightLoop()
|
|
20
|
+
* await actions.fightLoop({ eatAt: 20, duration: 20000 })
|
|
21
|
+
* await actions.fightLoop({ target: /vex/i })
|
|
22
|
+
*/
|
|
23
|
+
export default async function fightLoop(bot: any, sdk: any, opts: any = {}) {
|
|
24
|
+
const eatAt = opts.eatAt ?? 25;
|
|
25
|
+
const emergencyHp = 13; // one-hit kill range — eat no matter what
|
|
26
|
+
const maxEatHp = 28; // don't eat above this (wastes food: 40 max - 12 heal)
|
|
27
|
+
const duration = opts.duration ?? 15000;
|
|
28
|
+
const targetPattern = opts.target ?? null;
|
|
29
|
+
const fleeAt = opts.fleeAt ?? 3;
|
|
30
|
+
const endTime = Date.now() + duration;
|
|
31
|
+
|
|
32
|
+
let lastTarget: string | null = null;
|
|
33
|
+
|
|
34
|
+
while (Date.now() < endTime) {
|
|
35
|
+
const state = sdk.getState();
|
|
36
|
+
if (!state) { await sdk.waitForTicks(1); continue; }
|
|
37
|
+
|
|
38
|
+
const hp = state.player.hitpoints;
|
|
39
|
+
const players = sdk.getNearbyPlayers();
|
|
40
|
+
|
|
41
|
+
// Bail signal: low food + outnumbered
|
|
42
|
+
const foodCount = countFood(sdk);
|
|
43
|
+
if (foodCount <= fleeAt && players.length > 1) {
|
|
44
|
+
return {
|
|
45
|
+
hp,
|
|
46
|
+
maxHp: state.player.hitpointsBase ?? 0,
|
|
47
|
+
foodLeft: foodCount,
|
|
48
|
+
target: lastTarget,
|
|
49
|
+
nearbyPlayers: players,
|
|
50
|
+
fled: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Priority 1: Attack (deal damage BEFORE eating — tick-optimal)
|
|
55
|
+
let target;
|
|
56
|
+
if (targetPattern) {
|
|
57
|
+
target = players.find((p: any) =>
|
|
58
|
+
typeof targetPattern === 'string'
|
|
59
|
+
? p.name.toLowerCase() === targetPattern.toLowerCase()
|
|
60
|
+
: targetPattern.test(p.name)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Default: attack nearest player (simplest, most reliable)
|
|
65
|
+
if (!target && players.length > 0) {
|
|
66
|
+
target = players.sort((a: any, b: any) => a.distance - b.distance)[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (target) {
|
|
70
|
+
lastTarget = target.name;
|
|
71
|
+
try {
|
|
72
|
+
await bot.attackPlayer(target);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Target may have died or moved — continue loop
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Priority 2: Eat AFTER attacking (3-tick eat delay fits inside attack cooldown)
|
|
79
|
+
const shouldEat = hp <= emergencyHp || (hp < eatAt && hp <= maxEatHp);
|
|
80
|
+
if (shouldEat) {
|
|
81
|
+
const food = findBestFood(sdk);
|
|
82
|
+
if (food) {
|
|
83
|
+
await bot.eatFood(food);
|
|
84
|
+
await sdk.waitForTicks(1);
|
|
85
|
+
continue; // re-engage immediately
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await sdk.waitForTicks(2);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Return status
|
|
93
|
+
const finalState = sdk.getState();
|
|
94
|
+
return {
|
|
95
|
+
hp: finalState?.player?.hitpoints ?? 0,
|
|
96
|
+
maxHp: finalState?.player?.hitpointsBase ?? 0,
|
|
97
|
+
foodLeft: countFood(sdk),
|
|
98
|
+
target: lastTarget,
|
|
99
|
+
nearbyPlayers: sdk.getNearbyPlayers(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findBestFood(sdk: any): any {
|
|
104
|
+
return sdk.findInventoryItem(/swordfish/i)
|
|
105
|
+
|| sdk.findInventoryItem(/lobster/i)
|
|
106
|
+
|| sdk.findInventoryItem(/salmon/i)
|
|
107
|
+
|| sdk.findInventoryItem(/trout/i)
|
|
108
|
+
|| sdk.findInventoryItem(/bread/i)
|
|
109
|
+
|| sdk.findInventoryItem(/meat/i);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function countFood(sdk: any): number {
|
|
113
|
+
const inv = sdk.getInventory();
|
|
114
|
+
return inv.filter((i: any) =>
|
|
115
|
+
/swordfish|lobster|salmon|trout|bread|meat/i.test(i.name)
|
|
116
|
+
).length;
|
|
117
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retreat from the nearest enemy while eating food to recover HP.
|
|
3
|
+
*
|
|
4
|
+
* Walks away from the closest player while prioritizing eating.
|
|
5
|
+
* Uses tick-optimal eating: won't eat above HP 28 (40 max - 12 heal = waste).
|
|
6
|
+
* Stops when HP is above the safe threshold or food/time runs out.
|
|
7
|
+
*
|
|
8
|
+
* @param opts.safeHp - HP to heal to before stopping (default: 40)
|
|
9
|
+
* @param opts.duration - Max time to kite in ms (default: 10000)
|
|
10
|
+
* @param opts.direction - "north"|"south"|"east"|"west"|"away" (default: "away" from nearest enemy)
|
|
11
|
+
* @returns { hp, foodLeft, escaped } — status after kiting
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* await actions.kite()
|
|
15
|
+
* await actions.kite({ safeHp: 35, direction: "north" })
|
|
16
|
+
*/
|
|
17
|
+
export default async function kite(bot: any, sdk: any, opts: any = {}) {
|
|
18
|
+
const safeHp = opts.safeHp ?? 40;
|
|
19
|
+
const duration = opts.duration ?? 10000;
|
|
20
|
+
const direction = opts.direction ?? 'away';
|
|
21
|
+
const maxEatHp = 28; // don't eat above this (wastes food)
|
|
22
|
+
const endTime = Date.now() + duration;
|
|
23
|
+
|
|
24
|
+
const STEP_SIZE = 5; // tiles per retreat step
|
|
25
|
+
|
|
26
|
+
while (Date.now() < endTime) {
|
|
27
|
+
const state = sdk.getState();
|
|
28
|
+
if (!state) { await sdk.waitForTicks(1); continue; }
|
|
29
|
+
|
|
30
|
+
const hp = state.player.hitpoints;
|
|
31
|
+
|
|
32
|
+
// Check if we've healed enough
|
|
33
|
+
if (hp >= safeHp) {
|
|
34
|
+
return { hp, foodLeft: countFood(sdk), escaped: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Eat if below the waste ceiling (don't eat at 29+ HP — wastes healing)
|
|
38
|
+
if (hp <= maxEatHp) {
|
|
39
|
+
const food = findBestFood(sdk);
|
|
40
|
+
if (food) {
|
|
41
|
+
await bot.eatFood(food);
|
|
42
|
+
} else {
|
|
43
|
+
// No food left, no point kiting
|
|
44
|
+
return { hp, foodLeft: 0, escaped: false };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Calculate retreat direction
|
|
49
|
+
const myX = state.player.worldX;
|
|
50
|
+
const myZ = state.player.worldZ;
|
|
51
|
+
let dx = 0, dz = 0;
|
|
52
|
+
|
|
53
|
+
if (direction === 'away') {
|
|
54
|
+
const players = sdk.getNearbyPlayers();
|
|
55
|
+
if (players.length > 0) {
|
|
56
|
+
const nearest = players.sort((a: any, b: any) => a.distance - b.distance)[0];
|
|
57
|
+
// Move opposite direction from nearest player
|
|
58
|
+
dx = myX - nearest.x;
|
|
59
|
+
dz = myZ - nearest.z;
|
|
60
|
+
// Normalize to step size
|
|
61
|
+
const dist = Math.sqrt(dx * dx + dz * dz) || 1;
|
|
62
|
+
dx = Math.round((dx / dist) * STEP_SIZE);
|
|
63
|
+
dz = Math.round((dz / dist) * STEP_SIZE);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
switch (direction) {
|
|
67
|
+
case 'north': dz = STEP_SIZE; break;
|
|
68
|
+
case 'south': dz = -STEP_SIZE; break;
|
|
69
|
+
case 'east': dx = STEP_SIZE; break;
|
|
70
|
+
case 'west': dx = -STEP_SIZE; break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (dx !== 0 || dz !== 0) {
|
|
75
|
+
try {
|
|
76
|
+
await bot.walkTo(myX + dx, myZ + dz, 2);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Hit a wall or boundary — try different direction
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await sdk.waitForTicks(2);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const finalHp = sdk.getState()?.player?.hitpoints ?? 0;
|
|
86
|
+
return { hp: finalHp, foodLeft: countFood(sdk), escaped: finalHp >= safeHp };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function findBestFood(sdk: any): any {
|
|
90
|
+
return sdk.findInventoryItem(/swordfish/i)
|
|
91
|
+
|| sdk.findInventoryItem(/lobster/i)
|
|
92
|
+
|| sdk.findInventoryItem(/salmon/i)
|
|
93
|
+
|| sdk.findInventoryItem(/trout/i)
|
|
94
|
+
|| sdk.findInventoryItem(/bread/i)
|
|
95
|
+
|| sdk.findInventoryItem(/meat/i);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function countFood(sdk: any): number {
|
|
99
|
+
const inv = sdk.getInventory();
|
|
100
|
+
return inv.filter((i: any) =>
|
|
101
|
+
/swordfish|lobster|salmon|trout|bread|meat/i.test(i.name)
|
|
102
|
+
).length;
|
|
103
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan for nearby ground items, pick up the best available, and equip gear.
|
|
3
|
+
*
|
|
4
|
+
* Prioritizes weapons > armor > food. Picks up items closest to you first
|
|
5
|
+
* within each priority tier. Equips weapons and armor automatically.
|
|
6
|
+
*
|
|
7
|
+
* @param opts.maxItems - Max items to pick up before stopping (default: 5)
|
|
8
|
+
* @param opts.radius - Only grab items within this distance (default: 10)
|
|
9
|
+
* @param opts.foodOnly - Only pick up food, skip weapons/armor (default: false)
|
|
10
|
+
* @returns { picked, equipped, inventory } — what was grabbed and equipped
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* await actions.lootAndEquip()
|
|
14
|
+
* await actions.lootAndEquip({ maxItems: 3, foodOnly: true })
|
|
15
|
+
*/
|
|
16
|
+
export default async function lootAndEquip(bot: any, sdk: any, opts: any = {}) {
|
|
17
|
+
const maxItems = opts.maxItems ?? 5;
|
|
18
|
+
const maxRadius = opts.radius ?? 10;
|
|
19
|
+
const foodOnly = opts.foodOnly ?? false;
|
|
20
|
+
|
|
21
|
+
const picked: string[] = [];
|
|
22
|
+
const equipped: string[] = [];
|
|
23
|
+
|
|
24
|
+
// Item tier lists (best first)
|
|
25
|
+
const weapons = [
|
|
26
|
+
/rune scimitar/i, /rune long/i,
|
|
27
|
+
/adamant sword/i, /mithril scimitar/i,
|
|
28
|
+
/iron sword/i, /bronze dagger/i,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const armor = [
|
|
32
|
+
/rune chain/i, /adamant plate/i,
|
|
33
|
+
/steel chain/i, /mithril.*helm/i,
|
|
34
|
+
/hardleather/i, /leather chaps/i, /leather body/i,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const food = [
|
|
38
|
+
/swordfish/i, /lobster/i,
|
|
39
|
+
/salmon/i, /trout/i,
|
|
40
|
+
/bread/i, /meat/i,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Build a prioritized pickup list
|
|
44
|
+
const priorities = foodOnly
|
|
45
|
+
? [...food]
|
|
46
|
+
: [...weapons, ...armor, ...food];
|
|
47
|
+
|
|
48
|
+
for (const pattern of priorities) {
|
|
49
|
+
if (picked.length >= maxItems) break;
|
|
50
|
+
|
|
51
|
+
const item = sdk.findGroundItem(pattern);
|
|
52
|
+
if (item && item.distance <= maxRadius) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await bot.pickupItem(item);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
picked.push(item.name);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// Item may have been grabbed by someone else
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Equip best weapon found (try best first)
|
|
65
|
+
if (!foodOnly) {
|
|
66
|
+
for (const pattern of weapons) {
|
|
67
|
+
const item = sdk.findInventoryItem(pattern);
|
|
68
|
+
if (item) {
|
|
69
|
+
try {
|
|
70
|
+
await bot.equipItem(item);
|
|
71
|
+
equipped.push(item.name);
|
|
72
|
+
} catch (e) { /* already equipped or can't */ }
|
|
73
|
+
break; // Only equip one weapon
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Equip best armor found
|
|
78
|
+
for (const pattern of armor) {
|
|
79
|
+
const item = sdk.findInventoryItem(pattern);
|
|
80
|
+
if (item) {
|
|
81
|
+
try {
|
|
82
|
+
await bot.equipItem(item);
|
|
83
|
+
equipped.push(item.name);
|
|
84
|
+
} catch (e) { /* already equipped or can't */ }
|
|
85
|
+
// Don't break — can equip body + legs + helm
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
picked,
|
|
92
|
+
equipped,
|
|
93
|
+
inventory: sdk.getInventory(),
|
|
94
|
+
};
|
|
95
|
+
}
|