From a0963747b667c5509583e4a640a528bb241ac792 Mon Sep 17 00:00:00 2001 From: lyizai <3945107889@qq.com> Date: Fri, 21 Aug 2026 16:25:00 +0800 Subject: [PATCH] =?UTF-8?q?v1.2.4:=20=E5=AF=BB=E8=B7=AF=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E8=80=97=E5=B0=BD=E5=85=88=E5=BE=80=E8=BE=B9=E4=B8=8A=E8=B5=B0?= =?UTF-8?q?=E4=B8=A4=E6=AD=A5=E5=86=8D=E9=87=8D=E8=AF=95=E5=8E=9F=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=EF=BC=8C=E4=BB=8D=E5=A4=B1=E8=B4=A5=E6=89=8D=E5=9B=9E?= =?UTF-8?q?=E5=9F=8E=E6=88=96=E5=88=87=E6=8D=A2=E7=9B=AE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExpansionController.java | 144 ++++++++++++++++-- 1 file changed, 130 insertions(+), 14 deletions(-) diff --git a/src/main/java/autostore/client/controller/ExpansionController.java b/src/main/java/autostore/client/controller/ExpansionController.java index 525d5c7..28e3489 100644 --- a/src/main/java/autostore/client/controller/ExpansionController.java +++ b/src/main/java/autostore/client/controller/ExpansionController.java @@ -33,6 +33,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.client.MinecraftClient; import net.minecraft.component.DataComponentTypes; import net.minecraft.text.Text; @@ -99,6 +100,18 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { private long consecutiveTimeoutMillis; /** 发送 /u return 回城指令的时间戳(RETURNING 状态使用,用于等待传送完成) */ 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) { this.dataService = dataService; @@ -216,6 +229,8 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { this.completedClaims = 0; this.retryCount = 0; this.consecutiveTimeoutMillis = 0L; + this.sideStepAttempted = false; + this.sideStepping = false; this.queue.clear(); this.claimedSet.clear(); this.target = null; @@ -239,6 +254,10 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { return; } this.baritoneBridge.cancel(); + // 若正处于"往边上走两步"阶段,先释放手动按下的前进键 + if (this.sideStepping) { + this.stopSideStep(false); + } this.eating = false; this.setState(State.STOPPED, "\u5df2\u624b\u52a8\u505c\u6b62"); this.log("\u5df2\u624b\u52a8\u505c\u6b62"); @@ -278,6 +297,11 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { return; } this.tickAutoSwim(client); + // 处于"往边上走两步"阶段:每帧驱动手动横向行走,走开后自动重试原目标 + if (this.sideStepping) { + this.tickSideStep(); + return; + } switch (this.state.ordinal()) { case 1: { this.plan(); @@ -346,6 +370,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { /** 让 Baritone 寻路前往当前目标区块,并进入 PATHING 状态 */ private void startPathing() { this.retryCount = 0; + this.sideStepAttempted = false; this.stageStartedMillis = System.currentTimeMillis(); this.lastProgress = 0; this.baritoneBridge.pathTo(this.target); @@ -355,7 +380,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { /** * 寻路阶段每帧逻辑:到达目标区块后进入等待开垦状态; - * 若寻路超时则重新寻路(而非回城),超过重试次数则跳过该目标。 + * 若寻路超时则重新寻路;重试耗尽先"往边上走两步"再重试,仍失败则回城(连续超时达阈值)或跳过该目标。 */ private void tickPathing() { if (this.target == null) { @@ -373,8 +398,12 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { this.pathCalcFailed = false; ++this.retryCount; 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 { 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(); @@ -401,18 +430,18 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { // 主动刷新地图,尽快同步最新领土状态 this.dataService.refreshAsync(); // 累计"连续寻路超时"时长:连续多次寻路都超时说明玩家被地形/缝隙卡死走不到目标, - // 累计达到阈值后发送 /u return 回城(约 10 秒后传送到可寻路位置),传送后重新规划。 + // 达到阈值后作为发送 /u return 回城(约 10 秒后传送到可寻路位置)的最后手段,平时不打断重试/走两步。 this.consecutiveTimeoutMillis += (long)this.config.waitTimeoutSeconds * 1000L; - if (this.consecutiveTimeoutMillis >= (long)this.config.returnThresholdSeconds * 1000L) { - 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 { - // 重新寻路(而非回城):取消旧路径并重新计算,往往“在边上走走”就能走出困境 + if (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"); + } else if (!this.sideStepAttempted) { + // 重试耗尽:先不切换目标,用"往边上走两步"再重试一次(走开障碍后重新寻路原目标) + this.startSideStep(); + } else { + // 走两步也未能脱困:连续超时达到阈值则回城传送重新开始,否则切换目标 + this.giveUpTarget("\u5bfb\u8def\u8d85\u65f6"); } } } @@ -454,6 +483,93 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { 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() { if (this.target == null) { @@ -805,10 +921,10 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { return -1; } - /** 进食完成后恢复任务:若仍在寻路阶段则重新发起寻路 */ + /** 进食完成后恢复任务:若仍在寻路阶段则重新发起寻路(走两步阶段不抢路) */ private void resumeAfterEating() { 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.baritoneBridge.pathTo(this.target); }