feat: 用 Node.js + mineflayer 重构 AutoStore,UI 改为网页控制台并适配 Grim 反作弊

由 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 语法自检通过。
这个提交包含在:
lyizai
2026-09-27 17:17:41 +08:00
当前提交 a12a24c716
共修改 44 个文件,包含 12225 行新增和 0 行删除
+68
查看文件
@@ -0,0 +1,68 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { defaultConfig, normalizeConfig } = require('./expansionConfig');
/**
* 配置文件读写器:把扩地配置持久化为 JSON。
*
* 默认路径为项目根目录下的 `data/autostore.json`(已在 .gitignore 中忽略),
* 也可通过构造参数覆盖,便于测试使用临时目录。
*/
class ConfigStore {
/**
* @param {string} [filePath] 配置文件绝对路径
*/
constructor(filePath) {
this.filePath = filePath || path.join(__dirname, '..', '..', 'data', 'autostore.json');
}
/**
* 从磁盘加载配置;文件不存在或解析失败时返回默认配置。
* @returns {object}
*/
load() {
let raw;
try {
raw = fs.readFileSync(this.filePath, 'utf8');
} catch {
return defaultConfig();
}
try {
const parsed = JSON.parse(raw);
const config = normalizeConfig(parsed);
// 固定值字段:防止旧配置文件里的异常值影响运行
config.autoEat = true;
config.eatThreshold = 14;
// API Key 兜底:配置文件未填写时从环境变量 SIMP_API_KEY 读取
//(密钥不进代码仓库,开发环境由 .env 提供,运行环境可设置系统环境变量)
if (!config.apiKey) {
const envKey = process.env.SIMP_API_KEY;
if (envKey && envKey.trim()) config.apiKey = envKey.trim();
}
return config;
} catch {
return defaultConfig();
}
}
/**
* 把配置写入磁盘(自动创建父目录)。
* @param {object} config
* @returns {boolean} 是否写入成功
*/
save(config) {
try {
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
fs.writeFileSync(this.filePath, JSON.stringify(config, null, 2), 'utf8');
return true;
} catch {
// 保存失败时静默忽略(不阻塞运行)
return false;
}
}
}
module.exports = { ConfigStore };