feat: 掉线重连后自动恢复扩地任务(autoResumeAfterReconnect)
本服会周期性把机器人踢下线(实测约 1~6 分钟一次,踢出原因是 "An internal error occurred in your connection"),每次都会走到「世界已退出,任务停止」。 原来的行为是就此停住 —— 机器人虽然能被原点自救送回真实世界(实测 4/4 成功), 却再也不会继续扩地,于是永远停在原地。 现在世界退出时把本次任务的配置记下来(resumeConfig), 重新进入世界后自动 start() 接上;状态文案改为「世界已退出,等待重连后自动恢复」, 并打印「已重新进入世界,自动恢复扩地任务」。 只有因世界退出而停的那次会登记:手动停止 / 到开垦时段外 / 耐久保护停机 都会在 stop() 里清空 resumeConfig,不会被重连重新拉起。 开关 autoResumeAfterReconnect 默认开启,可关。 测试 470 → 473。
这个提交包含在:
+21
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
|
本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||||
|
|
||||||
|
## [1.9.3] - 2026-10-01
|
||||||
|
|
||||||
|
**掉线重连后自动恢复扩地任务** —— 这是让扩地能真正连续跑起来的最后一环。
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- **`autoResumeAfterReconnect`(默认开启)**:本服会周期性把机器人踢下线
|
||||||
|
(实测约 1~6 分钟一次,踢出原因是 `An internal error occurred in your connection`),
|
||||||
|
每次都会走到「世界已退出,任务停止」。原来的行为是就此停住 ——
|
||||||
|
机器人虽然能被**原点自救**送回真实世界(实测 4/4 成功),却再也不会继续扩地,
|
||||||
|
于是永远停在原地。
|
||||||
|
现在世界退出时会把本次任务的配置记下来,重新进入世界后自动 `start()` 接上。
|
||||||
|
手动停止 / 到开垦时段外 / 耐久保护停机**不会**被自动恢复(只有因世界退出而停的那次会登记)。
|
||||||
|
- 状态文案改为「世界已退出,等待重连后自动恢复」,日志会打出
|
||||||
|
「已重新进入世界,自动恢复扩地任务」。
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
新增 3 个用例(470 → 473):世界退出后暂停并在重连时恢复、开关可关闭、
|
||||||
|
手动停止不会被重连重新拉起。
|
||||||
|
|
||||||
## [1.9.2] - 2026-10-01
|
## [1.9.2] - 2026-10-01
|
||||||
|
|
||||||
修正物理录制器里一个会误导排查的分类错误。
|
修正物理录制器里一个会误导排查的分类错误。
|
||||||
|
|||||||
自动生成
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.9.2",
|
"version": "1.9.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.9.2",
|
"version": "1.9.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.9.2",
|
"version": "1.9.3",
|
||||||
"description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)",
|
"description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ function defaultConfig() {
|
|||||||
durabilityGuard: true,
|
durabilityGuard: true,
|
||||||
/** 耐久保护阈值:剩余耐久百分比 ≤ 该值时回城(1-99) */
|
/** 耐久保护阈值:剩余耐久百分比 ≤ 该值时回城(1-99) */
|
||||||
durabilityThreshold: 15,
|
durabilityThreshold: 15,
|
||||||
|
/**
|
||||||
|
* 掉线重连后是否自动恢复被中断的扩地任务。
|
||||||
|
*
|
||||||
|
* 本服会周期性把机器人踢下线(实测约 1~6 分钟一次),每次都会触发
|
||||||
|
* 「世界已退出,任务停止」。若不自动恢复,任务就永远停在那里 ——
|
||||||
|
* 机器人虽然能被原点自救送回真实世界,却再也不会继续扩地。
|
||||||
|
* 手动停止 / 到时段外 / 耐久保护停机都不会被自动恢复。
|
||||||
|
*/
|
||||||
|
autoResumeAfterReconnect: true,
|
||||||
/** 是否启用自动用船过水 */
|
/** 是否启用自动用船过水 */
|
||||||
autoBoat: false,
|
autoBoat: false,
|
||||||
/** 触发自动用船的水面最小宽度(格) */
|
/** 触发自动用船的水面最小宽度(格) */
|
||||||
@@ -495,6 +504,7 @@ function validateConfig(config) {
|
|||||||
config.claimEndHour = clampInt(config.claimEndHour, 1, 24, 22);
|
config.claimEndHour = clampInt(config.claimEndHour, 1, 24, 22);
|
||||||
config.eatThreshold = clampInt(config.eatThreshold, 0, 20, 14);
|
config.eatThreshold = clampInt(config.eatThreshold, 0, 20, 14);
|
||||||
config.durabilityThreshold = clampInt(config.durabilityThreshold, 1, 99, 15);
|
config.durabilityThreshold = clampInt(config.durabilityThreshold, 1, 99, 15);
|
||||||
|
config.autoResumeAfterReconnect = config.autoResumeAfterReconnect !== false;
|
||||||
config.autoBoatMinWidth = clampInt(config.autoBoatMinWidth, 1, 64, 4);
|
config.autoBoatMinWidth = clampInt(config.autoBoatMinWidth, 1, 64, 4);
|
||||||
config.autoBoatMinLength = clampInt(config.autoBoatMinLength, 1, 128, 6);
|
config.autoBoatMinLength = clampInt(config.autoBoatMinLength, 1, 128, 6);
|
||||||
config.refreshIntervalSeconds = clampInt(config.refreshIntervalSeconds, 10, 3600, 30);
|
config.refreshIntervalSeconds = clampInt(config.refreshIntervalSeconds, 10, 3600, 30);
|
||||||
|
|||||||
@@ -128,6 +128,15 @@ class ExpansionController {
|
|||||||
|
|
||||||
/** 等待开拓时段自动开始:不在时段时点击开始会暂存配置(null 表示无等待任务) */
|
/** 等待开拓时段自动开始:不在时段时点击开始会暂存配置(null 表示无等待任务) */
|
||||||
this.pendingStart = null;
|
this.pendingStart = null;
|
||||||
|
/**
|
||||||
|
* 被「世界退出」打断的任务配置:重连进入世界后用它自动恢复。
|
||||||
|
*
|
||||||
|
* 本服会周期性把机器人踢下线(实测约 1~6 分钟一次),而每次掉线都会走到
|
||||||
|
* 「世界已退出,任务停止」。没有这个字段的话,机器人虽然能被原点自救送回
|
||||||
|
* 真实世界,任务却再也不会继续 —— 扩地永远跑不长。
|
||||||
|
* 手动停止 / 到时段外 / 耐久保护停机都不会写入它。
|
||||||
|
*/
|
||||||
|
this.resumeConfig = null;
|
||||||
/** 连续寻路超时累计时长(毫秒) */
|
/** 连续寻路超时累计时长(毫秒) */
|
||||||
this.consecutiveTimeoutMillis = 0;
|
this.consecutiveTimeoutMillis = 0;
|
||||||
/** 发送回城指令的时间戳 */
|
/** 发送回城指令的时间戳 */
|
||||||
@@ -228,6 +237,8 @@ class ExpansionController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.config = requestedConfig;
|
this.config = requestedConfig;
|
||||||
|
// 显式启动的任务优先于「掉线待恢复」的那一次
|
||||||
|
this.resumeConfig = null;
|
||||||
// 现实时间限制:不在允许时段时登记「等待自动开始」,进入时段后由 tick() 自动启动
|
// 现实时间限制:不在允许时段时登记「等待自动开始」,进入时段后由 tick() 自动启动
|
||||||
if (!this.isWithinClaimTime()) {
|
if (!this.isWithinClaimTime()) {
|
||||||
this.pendingStart = requestedConfig;
|
this.pendingStart = requestedConfig;
|
||||||
@@ -308,6 +319,8 @@ class ExpansionController {
|
|||||||
/** 手动停止任务 */
|
/** 手动停止任务 */
|
||||||
stop() {
|
stop() {
|
||||||
const bm = this.botManager;
|
const bm = this.botManager;
|
||||||
|
// 任何显式停止都作废「掉线待恢复」——只有 tick() 里因世界退出而停的那次会重新登记
|
||||||
|
this.resumeConfig = null;
|
||||||
if (this.pendingStart) {
|
if (this.pendingStart) {
|
||||||
this.pendingStart = null;
|
this.pendingStart = null;
|
||||||
this.setState(State.IDLE, '已取消自动开始');
|
this.setState(State.IDLE, '已取消自动开始');
|
||||||
@@ -335,9 +348,24 @@ class ExpansionController {
|
|||||||
if (!this.config) return; // 尚未启动过任务,没有可驱动的状态
|
if (!this.config) return; // 尚未启动过任务,没有可驱动的状态
|
||||||
if (!bm.isSpawned()) {
|
if (!bm.isSpawned()) {
|
||||||
if (this.isRunning()) {
|
if (this.isRunning()) {
|
||||||
|
// 先记下这次任务的配置:本服会周期性把机器人踢下线,重连回来时若没人管,
|
||||||
|
// 任务就永远停在这里。stop() 会清空 resumeConfig,所以要在它之后重新登记。
|
||||||
|
const interrupted = this.config;
|
||||||
this.stop();
|
this.stop();
|
||||||
this.status = '世界已退出,任务停止';
|
this.status = '世界已退出,等待重连后自动恢复';
|
||||||
|
if (interrupted && interrupted.autoResumeAfterReconnect !== false) {
|
||||||
|
this.resumeConfig = interrupted;
|
||||||
|
this._log('世界已退出,任务已暂停;重新进入世界后会自动恢复');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 重新进入世界:把被掉线打断的任务接上
|
||||||
|
if (this.resumeConfig && !this.isRunning()) {
|
||||||
|
const resume = this.resumeConfig;
|
||||||
|
this.resumeConfig = null;
|
||||||
|
this._log('已重新进入世界,自动恢复扩地任务');
|
||||||
|
this.start(resume).catch((err) => this._log('自动恢复任务失败:' + err.message, 'error'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.playerChunk = this._playerChunk();
|
this.playerChunk = this._playerChunk();
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class FakeBotManager {
|
|||||||
this.connection = { username: 'TestBot' };
|
this.connection = { username: 'TestBot' };
|
||||||
this.commands = [];
|
this.commands = [];
|
||||||
this._model = model;
|
this._model = model;
|
||||||
|
/** 设为 false 模拟「世界已退出」(掉线 / 被踢 / 被切到登录服) */
|
||||||
|
this.spawned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
isReady() {
|
isReady() {
|
||||||
@@ -94,7 +96,7 @@ class FakeBotManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSpawned() {
|
isSpawned() {
|
||||||
return true;
|
return this.spawned;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCommand(command) {
|
sendCommand(command) {
|
||||||
@@ -147,6 +149,46 @@ function setup(options) {
|
|||||||
return { controller, botManager, dataService, model, bot, logger, config };
|
return { controller, botManager, dataService, model, bot, logger, config };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('世界退出后任务暂停,重连进入世界时自动恢复', async () => {
|
||||||
|
// 本服会周期性把机器人踢下线(实测约 1~6 分钟一次)。没有自动恢复的话,
|
||||||
|
// 机器人虽然能被原点自救送回真实世界,任务却再也不会继续 —— 扩地永远跑不长。
|
||||||
|
const { controller, botManager, config } = setup();
|
||||||
|
await controller.start(config);
|
||||||
|
assert.strictEqual(controller.isRunning(), true);
|
||||||
|
|
||||||
|
botManager.spawned = false;
|
||||||
|
controller.tick();
|
||||||
|
assert.strictEqual(controller.isRunning(), false, '世界退出应停止任务');
|
||||||
|
assert.match(controller.status, /等待重连后自动恢复/);
|
||||||
|
|
||||||
|
botManager.spawned = true;
|
||||||
|
controller.tick();
|
||||||
|
assert.strictEqual(controller.isRunning(), true, '重连后应自动恢复任务');
|
||||||
|
assert.ok(controller.target, '恢复后应重新选出目标区块');
|
||||||
|
assert.strictEqual(botManager.pathing.pathCalls, 2, '恢复后应重新下发寻路');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('自动恢复可以关闭', async () => {
|
||||||
|
const { controller, botManager, config } = setup({ config: { autoResumeAfterReconnect: false } });
|
||||||
|
await controller.start(config);
|
||||||
|
botManager.spawned = false;
|
||||||
|
controller.tick();
|
||||||
|
botManager.spawned = true;
|
||||||
|
controller.tick();
|
||||||
|
assert.strictEqual(controller.isRunning(), false, '关掉开关后不应自动恢复');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('手动停止的任务不会被自动恢复', async () => {
|
||||||
|
const { controller, botManager, config } = setup();
|
||||||
|
await controller.start(config);
|
||||||
|
controller.stop();
|
||||||
|
botManager.spawned = false;
|
||||||
|
controller.tick();
|
||||||
|
botManager.spawned = true;
|
||||||
|
controller.tick();
|
||||||
|
assert.strictEqual(controller.isRunning(), false, '手动停止不应被重连重新拉起');
|
||||||
|
});
|
||||||
|
|
||||||
test('start:自动识别所在邦国并下发开垦模式指令', async () => {
|
test('start:自动识别所在邦国并下发开垦模式指令', async () => {
|
||||||
const { controller, botManager, config, model } = setup({ selfName: '' });
|
const { controller, botManager, config, model } = setup({ selfName: '' });
|
||||||
const ok = await controller.start(config);
|
const ok = await controller.start(config);
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户