v1.2.4: 寻路重试耗尽先往边上走两步再重试原目标,仍失败才回城或切换目标
这个提交包含在:
@@ -33,6 +33,7 @@ import net.minecraft.entity.player.PlayerEntity;
|
|||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.component.DataComponentTypes;
|
import net.minecraft.component.DataComponentTypes;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
@@ -99,6 +100,18 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
private long consecutiveTimeoutMillis;
|
private long consecutiveTimeoutMillis;
|
||||||
/** 发送 /u return 回城指令的时间戳(RETURNING 状态使用,用于等待传送完成) */
|
/** 发送 /u return 回城指令的时间戳(RETURNING 状态使用,用于等待传送完成) */
|
||||||
private long returnStartMillis;
|
private long returnStartMillis;
|
||||||
|
/** 当前目标是否已使用过"往边上走两步"策略(重试耗尽时先用它,避免重复;再次耗尽才切换目标) */
|
||||||
|
private boolean sideStepAttempted;
|
||||||
|
/** 是否正在"往边上走两步"阶段(手动向目标连线垂直方向走开约 2.5 格再重试) */
|
||||||
|
private boolean sideStepping;
|
||||||
|
/** 边走两步的方向符号(+1 左侧 / -1 右侧),被挡时换另一侧 */
|
||||||
|
private int sideStepSign = 1;
|
||||||
|
/** 边走两步的整体超时截止时间戳(毫秒) */
|
||||||
|
private long sideStepDeadlineMillis;
|
||||||
|
/** 最近一次换向/开始的时间戳(毫秒,用于判断该方向是否卡住) */
|
||||||
|
private long sideStepSinceMillis;
|
||||||
|
/** 边走两步开始时的玩家方块坐标(用于判断是否已走开约 2.5 格) */
|
||||||
|
private BlockPos sideStepStartPos;
|
||||||
|
|
||||||
public ExpansionController(TerritoryDataService dataService) {
|
public ExpansionController(TerritoryDataService dataService) {
|
||||||
this.dataService = dataService;
|
this.dataService = dataService;
|
||||||
@@ -216,6 +229,8 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
this.completedClaims = 0;
|
this.completedClaims = 0;
|
||||||
this.retryCount = 0;
|
this.retryCount = 0;
|
||||||
this.consecutiveTimeoutMillis = 0L;
|
this.consecutiveTimeoutMillis = 0L;
|
||||||
|
this.sideStepAttempted = false;
|
||||||
|
this.sideStepping = false;
|
||||||
this.queue.clear();
|
this.queue.clear();
|
||||||
this.claimedSet.clear();
|
this.claimedSet.clear();
|
||||||
this.target = null;
|
this.target = null;
|
||||||
@@ -239,6 +254,10 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.baritoneBridge.cancel();
|
this.baritoneBridge.cancel();
|
||||||
|
// 若正处于"往边上走两步"阶段,先释放手动按下的前进键
|
||||||
|
if (this.sideStepping) {
|
||||||
|
this.stopSideStep(false);
|
||||||
|
}
|
||||||
this.eating = false;
|
this.eating = false;
|
||||||
this.setState(State.STOPPED, "\u5df2\u624b\u52a8\u505c\u6b62");
|
this.setState(State.STOPPED, "\u5df2\u624b\u52a8\u505c\u6b62");
|
||||||
this.log("\u5df2\u624b\u52a8\u505c\u6b62");
|
this.log("\u5df2\u624b\u52a8\u505c\u6b62");
|
||||||
@@ -278,6 +297,11 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.tickAutoSwim(client);
|
this.tickAutoSwim(client);
|
||||||
|
// 处于"往边上走两步"阶段:每帧驱动手动横向行走,走开后自动重试原目标
|
||||||
|
if (this.sideStepping) {
|
||||||
|
this.tickSideStep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (this.state.ordinal()) {
|
switch (this.state.ordinal()) {
|
||||||
case 1: {
|
case 1: {
|
||||||
this.plan();
|
this.plan();
|
||||||
@@ -346,6 +370,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
/** 让 Baritone 寻路前往当前目标区块,并进入 PATHING 状态 */
|
/** 让 Baritone 寻路前往当前目标区块,并进入 PATHING 状态 */
|
||||||
private void startPathing() {
|
private void startPathing() {
|
||||||
this.retryCount = 0;
|
this.retryCount = 0;
|
||||||
|
this.sideStepAttempted = false;
|
||||||
this.stageStartedMillis = System.currentTimeMillis();
|
this.stageStartedMillis = System.currentTimeMillis();
|
||||||
this.lastProgress = 0;
|
this.lastProgress = 0;
|
||||||
this.baritoneBridge.pathTo(this.target);
|
this.baritoneBridge.pathTo(this.target);
|
||||||
@@ -355,7 +380,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 寻路阶段每帧逻辑:到达目标区块后进入等待开垦状态;
|
* 寻路阶段每帧逻辑:到达目标区块后进入等待开垦状态;
|
||||||
* 若寻路超时则重新寻路(而非回城),超过重试次数则跳过该目标。
|
* 若寻路超时则重新寻路;重试耗尽先"往边上走两步"再重试,仍失败则回城(连续超时达阈值)或跳过该目标。
|
||||||
*/
|
*/
|
||||||
private void tickPathing() {
|
private void tickPathing() {
|
||||||
if (this.target == null) {
|
if (this.target == null) {
|
||||||
@@ -373,8 +398,12 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
this.pathCalcFailed = false;
|
this.pathCalcFailed = false;
|
||||||
++this.retryCount;
|
++this.retryCount;
|
||||||
if (this.retryCount >= this.config.retries) {
|
if (this.retryCount >= this.config.retries) {
|
||||||
this.log("\u5bfb\u8def\u5931\u8d25\uff08\u76ee\u6807\u4e0d\u53ef\u8fbe\uff09\uff0c\u91cd\u8bd5\u8017\u5c3d\uff0c\u8df3\u8fc7 " + this.target.x() + "," + this.target.z());
|
// 重试耗尽:不直接切换目标,先"往边上走两步"再重试一次;仍失败才交给回城/切换处理
|
||||||
this.setState(State.FAIL, "\u5bfb\u8def\u5931\u8d25");
|
if (!this.sideStepAttempted) {
|
||||||
|
this.startSideStep();
|
||||||
|
} else {
|
||||||
|
this.giveUpTarget("\u5bfb\u8def\u5931\u8d25\uff08\u76ee\u6807\u4e0d\u53ef\u8fbe\uff09");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.log("\u5bfb\u8def\u5931\u8d25\uff08\u76ee\u6807\u4e0d\u53ef\u8fbe\uff09\uff0c\u91cd\u65b0\u5bfb\u8def " + this.retryCount + "/" + this.config.retries);
|
this.log("\u5bfb\u8def\u5931\u8d25\uff08\u76ee\u6807\u4e0d\u53ef\u8fbe\uff09\uff0c\u91cd\u65b0\u5bfb\u8def " + this.retryCount + "/" + this.config.retries);
|
||||||
this.dataService.refreshAsync();
|
this.dataService.refreshAsync();
|
||||||
@@ -401,18 +430,18 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
// 主动刷新地图,尽快同步最新领土状态
|
// 主动刷新地图,尽快同步最新领土状态
|
||||||
this.dataService.refreshAsync();
|
this.dataService.refreshAsync();
|
||||||
// 累计"连续寻路超时"时长:连续多次寻路都超时说明玩家被地形/缝隙卡死走不到目标,
|
// 累计"连续寻路超时"时长:连续多次寻路都超时说明玩家被地形/缝隙卡死走不到目标,
|
||||||
// 累计达到阈值后发送 /u return 回城(约 10 秒后传送到可寻路位置),传送后重新规划。
|
// 达到阈值后作为发送 /u return 回城(约 10 秒后传送到可寻路位置)的最后手段,平时不打断重试/走两步。
|
||||||
this.consecutiveTimeoutMillis += (long)this.config.waitTimeoutSeconds * 1000L;
|
this.consecutiveTimeoutMillis += (long)this.config.waitTimeoutSeconds * 1000L;
|
||||||
if (this.consecutiveTimeoutMillis >= (long)this.config.returnThresholdSeconds * 1000L) {
|
if (this.retryCount < this.config.retries) {
|
||||||
this.sendReturnCommand();
|
// 未耗尽:重新寻路(而非回城),取消旧路径并重新计算,往往"在边上走走"就能走出困境
|
||||||
} else if (this.retryCount >= this.config.retries) {
|
|
||||||
// 重试耗尽:跳过当前目标(不再回城),交给下一个目标
|
|
||||||
this.log("\u5bfb\u8def\u8d85\u65f6\uff0c\u8df3\u8fc7 " + this.target.x() + "," + this.target.z());
|
|
||||||
this.setState(State.FAIL, "\u5bfb\u8def\u8d85\u65f6");
|
|
||||||
} else {
|
|
||||||
// 重新寻路(而非回城):取消旧路径并重新计算,往往“在边上走走”就能走出困境
|
|
||||||
this.log("\u5bfb\u8def\u8d85\u65f6\uff0c\u91cd\u65b0\u5bfb\u8def " + this.retryCount + "/" + this.config.retries);
|
this.log("\u5bfb\u8def\u8d85\u65f6\uff0c\u91cd\u65b0\u5bfb\u8def " + this.retryCount + "/" + this.config.retries);
|
||||||
this.retrySameTarget("\u5bfb\u8def\u8d85\u65f6\uff0c\u91cd\u65b0\u5bfb\u8def");
|
this.retrySameTarget("\u5bfb\u8def\u8d85\u65f6\uff0c\u91cd\u65b0\u5bfb\u8def");
|
||||||
|
} else if (!this.sideStepAttempted) {
|
||||||
|
// 重试耗尽:先不切换目标,用"往边上走两步"再重试一次(走开障碍后重新寻路原目标)
|
||||||
|
this.startSideStep();
|
||||||
|
} else {
|
||||||
|
// 走两步也未能脱困:连续超时达到阈值则回城传送重新开始,否则切换目标
|
||||||
|
this.giveUpTarget("\u5bfb\u8def\u8d85\u65f6");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,6 +483,93 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
this.plan();
|
this.plan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目标寻路重试耗尽时的脱困策略入口:
|
||||||
|
* 取消当前寻路,改为手动朝"目标连线垂直方向"走开约 2.5 格(左右各试一侧),
|
||||||
|
* 走开后自动重新寻路原目标;走不开/超时则交给 {@link #giveUpTarget} 回城或切换目标。
|
||||||
|
* 每个目标最多触发一次(sideStepAttempted 标志在 startPathing 时重置)。
|
||||||
|
*/
|
||||||
|
private void startSideStep() {
|
||||||
|
this.baritoneBridge.cancel();
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
this.sideStepping = true;
|
||||||
|
this.sideStepAttempted = true;
|
||||||
|
this.sideStepSign = 1; // 默认先朝目标连线左侧走
|
||||||
|
this.sideStepStartPos = client != null && client.player != null ? client.player.getBlockPos() : null;
|
||||||
|
this.sideStepDeadlineMillis = System.currentTimeMillis() + 10000L; // 整体最多走 10 秒
|
||||||
|
this.sideStepSinceMillis = System.currentTimeMillis();
|
||||||
|
this.log("\u5bfb\u8def\u91cd\u8bd5\u8017\u5c3d\uff0c\u5f00\u59cb\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65\u518d\u91cd\u8bd5\u539f\u76ee\u6807");
|
||||||
|
this.setState(State.PATHING, "\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 走两步阶段每帧逻辑:朝目标连线垂直方向按住前进键行走,走开约 2.5 格后重试原目标 */
|
||||||
|
private void tickSideStep() {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
if (client == null || client.player == null || this.sideStepStartPos == null || this.target == null) {
|
||||||
|
this.stopSideStep(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 整体超时:往边上走两步也未能脱困
|
||||||
|
if (System.currentTimeMillis() >= this.sideStepDeadlineMillis) {
|
||||||
|
this.stopSideStep(false);
|
||||||
|
this.giveUpTarget("\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65\u8d85\u65f6");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 目标连线方向(水平),取垂直向量作为横向移动方向(左为 +,右为 -)
|
||||||
|
Vec3d playerPos = client.player.getPos();
|
||||||
|
Vec3d dirToTarget = new Vec3d(this.target.centerBlockX() + 0.5, playerPos.y, this.target.centerBlockZ() + 0.5)
|
||||||
|
.subtract(playerPos).normalize();
|
||||||
|
Vec3d sideDir = new Vec3d(-dirToTarget.z, 0.0, dirToTarget.x).multiply(this.sideStepSign);
|
||||||
|
// 面朝横向移动方向并按住前进键
|
||||||
|
float yaw = (float)Math.toDegrees(Math.atan2(sideDir.z, sideDir.x)) - 90.0f;
|
||||||
|
client.player.setYaw(yaw);
|
||||||
|
client.options.forwardKey.setPressed(true);
|
||||||
|
// 已走开足够距离(约 2.5 格):停止并重试原目标
|
||||||
|
if (client.player.getBlockPos().getSquaredDistance(this.sideStepStartPos) >= 6.25) {
|
||||||
|
this.stopSideStep(true);
|
||||||
|
this.retrySameTarget("\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65\u5b8c\u6210\uff0c\u91cd\u65b0\u5bfb\u8def");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 卡住检查:同一方向 5 秒没移动就换另一侧;两侧都试过仍不动则放弃
|
||||||
|
if (System.currentTimeMillis() - this.sideStepSinceMillis > 5000L) {
|
||||||
|
if (this.sideStepSign != -1) {
|
||||||
|
this.sideStepSign = -1;
|
||||||
|
this.sideStepSinceMillis = System.currentTimeMillis();
|
||||||
|
this.log("\u5f80\u8fb9\u4e0a\u8d70\u88ab\u6321\uff0c\u53d6\u53cd\u65b9\u5411\u7ee7\u7eed");
|
||||||
|
} else {
|
||||||
|
this.stopSideStep(false);
|
||||||
|
this.giveUpTarget("\u5f80\u8fb9\u4e0a\u8d70\u4e24\u4fa7\u5747\u88ab\u6321");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 结束走两步阶段:释放手动按下的前进键;success 表示已走开并准备重试原目标 */
|
||||||
|
private void stopSideStep(boolean success) {
|
||||||
|
this.sideStepping = false;
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
if (client != null && client.options != null) {
|
||||||
|
client.options.forwardKey.setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放弃当前目标(重试与走两步均已尝试)的统一出口:
|
||||||
|
* 若连续寻路超时累计已达到阈值,则发送 /u return 回城传送并重新规划;
|
||||||
|
* 否则记录日志并进入 FAIL 状态切换下一个目标。
|
||||||
|
*/
|
||||||
|
private void giveUpTarget(String reason) {
|
||||||
|
if (this.consecutiveTimeoutMillis >= (long)this.config.returnThresholdSeconds * 1000L) {
|
||||||
|
this.sendReturnCommand();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.target != null) {
|
||||||
|
this.log(reason + "\uff0c\u8df3\u8fc7 " + this.target.x() + "," + this.target.z());
|
||||||
|
} else {
|
||||||
|
this.log(reason);
|
||||||
|
}
|
||||||
|
this.setState(State.FAIL, reason);
|
||||||
|
}
|
||||||
|
|
||||||
/** 等待开垦阶段每帧逻辑:玩家离开目标区块则重新寻路;等待超时则重试或跳过当前目标 */
|
/** 等待开垦阶段每帧逻辑:玩家离开目标区块则重新寻路;等待超时则重试或跳过当前目标 */
|
||||||
private void tickWaiting() {
|
private void tickWaiting() {
|
||||||
if (this.target == null) {
|
if (this.target == null) {
|
||||||
@@ -805,10 +921,10 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 进食完成后恢复任务:若仍在寻路阶段则重新发起寻路 */
|
/** 进食完成后恢复任务:若仍在寻路阶段则重新发起寻路(走两步阶段不抢路) */
|
||||||
private void resumeAfterEating() {
|
private void resumeAfterEating() {
|
||||||
this.eating = false;
|
this.eating = false;
|
||||||
if (this.state == State.PATHING && this.target != null) {
|
if (this.state == State.PATHING && this.target != null && !this.sideStepping) {
|
||||||
this.stageStartedMillis = System.currentTimeMillis();
|
this.stageStartedMillis = System.currentTimeMillis();
|
||||||
this.baritoneBridge.pathTo(this.target);
|
this.baritoneBridge.pathTo(this.target);
|
||||||
}
|
}
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户