由 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 语法自检通过。
70 行
2.7 KiB
JavaScript
70 行
2.7 KiB
JavaScript
'use strict';
|
||
|
||
const test = require('node:test');
|
||
const assert = require('node:assert');
|
||
|
||
const chunkPos = require('../src/core/chunkPos');
|
||
|
||
test('fromBlock 向下取整且兼容负数坐标', () => {
|
||
assert.deepStrictEqual(chunkPos.fromBlock(0, 0), { x: 0, z: 0 });
|
||
assert.deepStrictEqual(chunkPos.fromBlock(15, 15), { x: 0, z: 0 });
|
||
assert.deepStrictEqual(chunkPos.fromBlock(16, 31), { x: 1, z: 1 });
|
||
// 负数必须向下取整到更小的区块号,而不是向零取整
|
||
assert.deepStrictEqual(chunkPos.fromBlock(-1, -16), { x: -1, z: -1 });
|
||
assert.deepStrictEqual(chunkPos.fromBlock(-17, -33), { x: -2, z: -3 });
|
||
});
|
||
|
||
test('key/decode 往返一致(含负数与边界)', () => {
|
||
const samples = [
|
||
[0, 0], [1, 0], [0, 1], [-1, 0], [0, -1], [-1, -1],
|
||
[1279, 1279], [-1280, -1280], [12345, -54321], [-2048, 2048]
|
||
];
|
||
for (const [x, z] of samples) {
|
||
const decoded = chunkPos.decode(chunkPos.key(x, z));
|
||
assert.deepStrictEqual(decoded, { x, z }, `坐标 ${x},${z} 编解码不一致`);
|
||
}
|
||
});
|
||
|
||
test('key 对不同坐标产生不同键', () => {
|
||
const seen = new Set();
|
||
for (let x = -50; x <= 50; x++) {
|
||
for (let z = -50; z <= 50; z++) {
|
||
const k = chunkPos.key(x, z);
|
||
assert.ok(!seen.has(k), `键冲突:${x},${z}`);
|
||
seen.add(k);
|
||
}
|
||
}
|
||
assert.strictEqual(seen.size, 101 * 101);
|
||
});
|
||
|
||
test('centerBlock 取区块中心(+8)', () => {
|
||
assert.strictEqual(chunkPos.centerBlockX(0), 8);
|
||
assert.strictEqual(chunkPos.centerBlockX(-1), -8);
|
||
assert.strictEqual(chunkPos.centerBlockZ(3), 56);
|
||
});
|
||
|
||
test('曼哈顿与切比雪夫距离', () => {
|
||
assert.strictEqual(chunkPos.manhattan({ x: 0, z: 0 }, { x: 3, z: -4 }), 7);
|
||
assert.strictEqual(chunkPos.chebyshev({ x: 0, z: 0 }, { x: 3, z: -4 }), 4);
|
||
assert.strictEqual(chunkPos.chebyshev({ x: 5, z: 5 }, { x: 5, z: 5 }), 0);
|
||
});
|
||
|
||
test('equals 处理 null 与同值', () => {
|
||
assert.strictEqual(chunkPos.equals({ x: 1, z: 2 }, { x: 1, z: 2 }), true);
|
||
assert.strictEqual(chunkPos.equals({ x: 1, z: 2 }, { x: 2, z: 1 }), false);
|
||
assert.strictEqual(chunkPos.equals(null, null), true);
|
||
assert.strictEqual(chunkPos.equals({ x: 1, z: 2 }, null), false);
|
||
});
|
||
|
||
test('cardinalNeighbors 返回四个正方向邻居', () => {
|
||
const neighbors = chunkPos.cardinalNeighbors(0, 0).map((c) => `${c.x},${c.z}`);
|
||
assert.deepStrictEqual(neighbors.sort(), ['-1,0', '0,-1', '0,1', '1,0']);
|
||
});
|
||
|
||
test('withinWorldBorder 以 ±1280 为界', () => {
|
||
assert.strictEqual(chunkPos.withinWorldBorder(0, 0), true);
|
||
assert.strictEqual(chunkPos.withinWorldBorder(1279, -1280), true);
|
||
assert.strictEqual(chunkPos.withinWorldBorder(1280, 0), false);
|
||
assert.strictEqual(chunkPos.withinWorldBorder(0, -1281), false);
|
||
});
|