由 Fabric 客户端模组 AutoStore v1.3.1 重构为独立 Node.js 机器人,无需游戏客户端。 核心实现: - 领土数据:Dynmap markers.json 拉取解析(奇偶填充识别国中飞地)、SimpCore 开放 API 第四数据源、 带缓存击穿的时间戳请求与内容未变化跳过解析 - 扩地规划:四种策略(方形外扩 / 区域 S 形扫描 / 玩家周围 / 绕国一圈)逐行对照移植, 接壤判定只算己方领土,飞地优先填补 - 扩地状态机:规划→寻路→等待开垦→成功/失败→下一目标→完成,含超时重试、 往边上走两步自救、连续超时回城、耐久保护回城 - 生存辅助:自动进食、自动攻击(豁免中立生物)、自动游泳防溺水、自动用船(默认关闭) - 寻路:Baritone → mineflayer-pathfinder,高度偏好用包装 Goal 等价移植 Grim 反作弊合规层(新增,核心差异点): - 旋转不超前:接管 bot.look 按 ≤8.6°/tick 推进朝向,消除 mineflayer 「本地朝向瞬间跳变、数据包滞后补发」导致的 Prediction 分歧 - 攻击三重校验:朝向已同步 + 距离 ≤3.0 格 + 视线可达 + 攻击冷却 + 非骑乘/进食 - 动作互斥:拦下疾跑+潜行、疾跑+使用物品等原版不可能出现的组合 - 物理参数只读自检 + 寻路约束(禁挖/禁放/禁跑酷/禁搭塔/限下落/绕开水域) 网页控制台(新增): - 状态面板、Canvas 领土地图(缩放/平移/图层)、分组配置表单、实时日志流 - REST + WebSocket 接口,支持 WEB_TOKEN 令牌鉴权 修复: - containsFuzzy 只取固定长度前缀的末行编辑距离,导致完全相同的子串也匹配不上 - 占领去重依赖 Set.add 返回值(JS 中恒为真),导致同一区块被重复计数 - 非法枚举值不再钳制到最近边界,改为回退默认值 测试:154 个用例,npm test 全绿;npm run check 语法自检通过。
116 行
4.4 KiB
JavaScript
116 行
4.4 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
|
|
const { fillTemplate } = require('../src/bot/botManager');
|
|
const { AutoEater, findFoodSlot } = require('../src/bot/autoEat');
|
|
const { DurabilityGuard, CHECK_INTERVAL_MS } = require('../src/bot/durabilityGuard');
|
|
const { defaultConfig } = require('../src/config/expansionConfig');
|
|
const { FakeBot } = require('./helpers/fakeBot');
|
|
|
|
test('fillTemplate 替换密码占位符', () => {
|
|
assert.strictEqual(fillTemplate('login {password}', 'abc123'), 'login abc123');
|
|
assert.strictEqual(fillTemplate('register {password} {password}', 'p'), 'register p p');
|
|
assert.strictEqual(fillTemplate('login', 'abc'), 'login', '无占位符时原样返回');
|
|
});
|
|
|
|
test('findFoodSlot 找到背包中的食物', () => {
|
|
const bot = new FakeBot({
|
|
slots: [
|
|
{ name: 'diamond_sword' },
|
|
{ name: 'cooked_beef' },
|
|
{ name: 'apple' }
|
|
]
|
|
});
|
|
assert.strictEqual(findFoodSlot(bot), 1);
|
|
});
|
|
|
|
test('findFoodSlot 没有食物时返回 -1', () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'diamond_sword' }, { name: 'stone' }] });
|
|
assert.strictEqual(findFoodSlot(bot), -1);
|
|
});
|
|
|
|
test('自动进食:饥饿低于阈值时开始进食', async () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'cooked_beef' }] });
|
|
bot.food = 10;
|
|
const config = defaultConfig();
|
|
const eater = new AutoEater(bot, config, null, null);
|
|
const busy = eater.tick();
|
|
assert.strictEqual(busy, true);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.strictEqual(bot.food, 20, '进食后饥饿值应回满');
|
|
assert.strictEqual(eater.eating, false, '进食完成后应复位');
|
|
});
|
|
|
|
test('自动进食:饥饿充足时不做任何事', () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'cooked_beef' }] });
|
|
bot.food = 20;
|
|
const eater = new AutoEater(bot, defaultConfig(), null, null);
|
|
assert.strictEqual(eater.tick(), false);
|
|
assert.strictEqual(eater.eating, false);
|
|
});
|
|
|
|
test('自动进食:背包没有食物时保持非占用状态', () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'stone' }] });
|
|
bot.food = 5;
|
|
const eater = new AutoEater(bot, defaultConfig(), null, null);
|
|
assert.strictEqual(eater.tick(), false);
|
|
assert.strictEqual(eater.eating, false);
|
|
});
|
|
|
|
test('自动进食:autoEat 关闭时完全跳过', () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'cooked_beef' }] });
|
|
bot.food = 5;
|
|
const config = defaultConfig();
|
|
config.autoEat = false;
|
|
const eater = new AutoEater(bot, config, null, null);
|
|
assert.strictEqual(eater.tick(), false);
|
|
});
|
|
|
|
test('自动进食:进食前松开移动键(防 NoSlow)', async () => {
|
|
const bot = new FakeBot({ slots: [{ name: 'cooked_beef' }] });
|
|
bot.food = 10;
|
|
const controls = {};
|
|
const grim = {
|
|
setControlState(control, state) { controls[control] = state; },
|
|
setLook() {}
|
|
};
|
|
const config = defaultConfig();
|
|
const eater = new AutoEater(bot, config, null, grim);
|
|
eater.tick();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
for (const control of ['forward', 'back', 'left', 'right', 'sprint', 'jump']) {
|
|
assert.strictEqual(controls[control], false, control + ' 应被松开');
|
|
}
|
|
});
|
|
|
|
test('耐久保护:按间隔节流检查', () => {
|
|
const bot = new FakeBot({ slots: [] });
|
|
bot.heldItem = { name: 'diamond_sword', maxDurability: 1561, durabilityUsed: 1500 };
|
|
const guard = new DurabilityGuard(bot, defaultConfig(), null);
|
|
|
|
const first = guard.check(1000000);
|
|
assert.ok(first, '首次检查应立即生效');
|
|
assert.strictEqual(guard.check(1000000 + CHECK_INTERVAL_MS - 1), null, '间隔内不再检查');
|
|
assert.ok(guard.check(1000000 + CHECK_INTERVAL_MS), '超过间隔后再次检查');
|
|
});
|
|
|
|
test('耐久保护:关闭时永远返回 null', () => {
|
|
const bot = new FakeBot({ slots: [] });
|
|
bot.heldItem = { name: 'diamond_sword', maxDurability: 1561, durabilityUsed: 1561 };
|
|
const config = defaultConfig();
|
|
config.durabilityGuard = false;
|
|
const guard = new DurabilityGuard(bot, config, null);
|
|
assert.strictEqual(guard.check(1000), null);
|
|
});
|
|
|
|
test('耐久保护:reset 后立即重新检查', () => {
|
|
const bot = new FakeBot({ slots: [] });
|
|
bot.heldItem = { name: 'diamond_sword', maxDurability: 1561, durabilityUsed: 1500 };
|
|
const guard = new DurabilityGuard(bot, defaultConfig(), null);
|
|
guard.check(1000000);
|
|
guard.reset();
|
|
assert.ok(guard.check(1000000), 'reset 后应可立即再次检查');
|
|
});
|