diff --git a/README.md b/README.md index 7157cf3..41a0756 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ - 从远程 `markers.json` 拉取各国领土边界 - 四种扩地策略:方形外扩、区域扩地、玩家周围、绕国一圈 - 区域扩地采用 S 形扫描:从与己方领土接壤的区块开始,横向/纵向 S 形扫过整个矩形,不沿邦国边界,但保证每个新占领区块都与**己方领土**接壤(仅四个正方向,不把其他邦国领土计入接壤) -- 自动寻路(Baritone)前往目标区块;监听 Baritone 路径事件,目标不可达(CALC_FAILED,如被悬崖/高山/水体隔断)时立即跳过;寻路超时则重新寻路(不立即回城),重试耗尽才跳过当前目标 -- 连续寻路超时回城:连续寻路超时累计超过 1 分钟(可配置)时自动发送 `/u return`,约 10 秒后传送到可寻路位置,随后重新规划并从最高优先级区块重新开始扩地 +- 自动寻路(Baritone)前往目标区块;监听 Baritone 路径事件,目标不可达(CALC_FAILED,如被悬崖/高山/水体隔断)时重试、耗尽后先"往边上走两步"再试,仍失败才切换目标;寻路超时则重新寻路(不立即回城) +- 高度偏好寻路:寻路优先往高处走(y 越大优先级越高,y 越小有惩罚),适合山区/高原目标;可通过配置关闭或调整权重 +- 连续寻路超时回城:连续寻路超时累计超过 1 分钟(可配置)时,先反复尝试"往边上走两步"脱困(默认最多 3 轮),仍无法脱困才自动发送 `/u return`,约 10 秒后传送到可寻路位置,随后重新规划并从最高优先级区块重新开始扩地 - 运行时接壤校验:寻路/等待阶段每帧校验目标是否仍与己方领土接壤,地图刷新导致边界变化时立即跳过 - 目标有效性校验:目标若已被其他邦国占领则立即跳过 - 现实时间限制:仅 11:00-22:00(本地现实时间)允许开拓,其余时段启动会被拒绝、运行中到点自动停止 @@ -25,7 +26,7 @@ ## 安装 1. 安装 Fabric Loader 0.16.14+ 和 Fabric API -2. 将 `autostore-1.2.4.jar` 放入 `.minecraft/mods/` 目录 +2. 将 `autostore-1.2.5.jar` 放入 `.minecraft/mods/` 目录 3. 启动游戏,按 `Ctrl+R` 打开 GUI ## 构建 @@ -35,7 +36,7 @@ ./build_mod.sh ``` -输出文件:`autostore-1.2.4.jar` +输出文件:`autostore-1.2.5.jar` ## 赞助支持 diff --git a/gradle.properties b/gradle.properties index 259d9ee..debb63b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ yarn_mappings=1.21.4+build.8 loader_version=0.19.3 # Mod Properties -mod_version=1.2.4 +mod_version=1.2.5 maven_group=autostore archives_base_name=autostore diff --git a/src/main/java/autostore/client/bridge/BaritoneBridge.java b/src/main/java/autostore/client/bridge/BaritoneBridge.java index c2ebb5a..fd2b243 100644 --- a/src/main/java/autostore/client/bridge/BaritoneBridge.java +++ b/src/main/java/autostore/client/bridge/BaritoneBridge.java @@ -11,6 +11,7 @@ import baritone.api.event.events.PathEvent; import baritone.api.event.listener.AbstractGameEventListener; import autostore.client.config.ExpansionConfig; import autostore.client.territory.ChunkPos; +import baritone.api.pathing.goals.Goal; import baritone.api.pathing.goals.GoalXZ; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -39,6 +40,8 @@ public final class BaritoneBridge { private final Set pathListeners = ConcurrentHashMap.newKeySet(); /** 是否已向 Baritone 注册事件监听器(只注册一次) */ private volatile boolean eventListenerRegistered; + /** 最近一次应用的行为配置(applyConfig 时保存,供 pathTo 选择寻路 Goal) */ + private ExpansionConfig config = new ExpansionConfig(); /** 注册一个路径事件监听器;同时确保已向 Baritone 事件总线挂载转发器 */ public void addPathListener(PathListener listener) { @@ -52,6 +55,7 @@ public final class BaritoneBridge { * @param config 扩地配置 */ public void applyConfig(ExpansionConfig config) { + this.config = config; Settings settings = BaritoneAPI.getSettings(); settings.allowSprint.value = config.allowSprint; settings.allowBreak.value = config.allowBreak; @@ -72,12 +76,15 @@ public final class BaritoneBridge { /** * 让 Baritone 寻路前往指定区块中心。 * - *

使用 Baritone 原生{@link GoalXZ}(目标区块中心单格):原生 GoalXZ 对 + *

默认使用 Baritone 原生{@link GoalXZ}(目标区块中心单格):原生 GoalXZ 对 * 可达地形几乎不会误判“目标不可达”,是最好的默认选择。早期用自定义 * {@code ChunkGoal}(曾加入高度硬约束)疑似是“平原上一按 W 就能走、Baritone 却秒 * CALC_FAILED”的根因——自定义 Goal 在某些站位/边界下会把可达目标判成不可达。 * 到达判定仍由控制器 {@code playerChunk.equals(target)} 保证,与 Goal 自身容差无关。

* + *

当开启高度偏好({@code altitudePreference})时改用 {@link GoalXZAltitude}: + * 到达判定与 GoalXZ 完全一致(水平到达),仅启发式成本偏向高处,引导寻路优先走高。

+ * * @param chunk 目标区块 */ public void pathTo(ChunkPos chunk) { @@ -85,7 +92,13 @@ public final class BaritoneBridge { if (baritone == null) { return; } - baritone.getCustomGoalProcess().setGoalAndPath(new GoalXZ(chunk.centerBlockX(), chunk.centerBlockZ())); + Goal goal; + if (this.config.altitudePreference) { + goal = new GoalXZAltitude(chunk.centerBlockX(), chunk.centerBlockZ(), this.config.altitudeWeight); + } else { + goal = new GoalXZ(chunk.centerBlockX(), chunk.centerBlockZ()); + } + baritone.getCustomGoalProcess().setGoalAndPath(goal); } /** 强制取消当前寻路任务。 */ diff --git a/src/main/java/autostore/client/bridge/GoalXZAltitude.java b/src/main/java/autostore/client/bridge/GoalXZAltitude.java new file mode 100644 index 0000000..e199f7a --- /dev/null +++ b/src/main/java/autostore/client/bridge/GoalXZAltitude.java @@ -0,0 +1,51 @@ +/* + * 本文件由 autostore-1.0-SNAPSHOT.jar 反编译并整理而来(CFR 0.152), + * 混淆名已替换为 Minecraft 1.21.4 Yarn 可读映射,并补充中文注释。 + */ +package autostore.client.bridge; + +import baritone.api.pathing.goals.Goal; +import baritone.api.pathing.goals.GoalXZ; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +/** + * 带高度偏好的区块中心寻路目标。 + * + *

到达判定与 Baritone 原生 {@link GoalXZ} 完全一致(水平到达目标区块中心即成功, + * 不约束高度,避免旧版 ChunkGoal 因高度硬约束误判“不可达”); + * 但启发式成本中加入高度项:y 越大启发成本越低(A* 更倾向走高处), + * y 越低成本越高(对走低处施惩罚),从而实现“寻路优先往高处走”。

+ */ +@Environment(EnvType.CLIENT) +public final class GoalXZAltitude implements Goal { + /** 目标区块中心 X */ + private final int x; + /** 目标区块中心 Z */ + private final int z; + /** 高度偏好权重:每上升 1 格减少的启发成本;越大越明显偏向高处 */ + private final double altitudeWeight; + + public GoalXZAltitude(int x, int z, double altitudeWeight) { + this.x = x; + this.z = z; + this.altitudeWeight = altitudeWeight; + } + + /** 到达判定与 GoalXZ 相同:水平坐标命中目标区块中心即算到达,不限制高度 */ + @Override + public boolean isInGoal(int x, int y, int z) { + return this.x == x && this.z == z; + } + + /** + * 启发式成本 = 水平基础成本(复用 GoalXZ 的对角线切比雪夫估计) - 高度奖励。 + * y 越高启发值越小(优先级越高),y 越启发值越大(惩罚);clamp 非负避免破坏 A* 排序。 + */ + @Override + public double heuristic(int x, int y, int z) { + double base = GoalXZ.calculate(x - this.x, z - this.z); + double altitudeCost = this.altitudeWeight * y; + return Math.max(0.0, base - altitudeCost); + } +} diff --git a/src/main/java/autostore/client/config/ExpansionConfig.java b/src/main/java/autostore/client/config/ExpansionConfig.java index 54d1676..6ae6b2d 100644 --- a/src/main/java/autostore/client/config/ExpansionConfig.java +++ b/src/main/java/autostore/client/config/ExpansionConfig.java @@ -37,6 +37,9 @@ public class ExpansionConfig { /** 发送 /u return 后等待传送完成的时间(秒);默认 12 秒,覆盖 10 秒传送延迟并留余量 */ public int returnWaitSeconds = 12; + /** 单个目标最多触发"往边上走两步"自救的轮数;每轮走两步后仍超时再重试,多轮无效后才回城/切换目标(默认 3) */ + public int sideStepMaxRounds = 3; + /** 区域扩地策略的中心 X 坐标(区块) */ public int regionCenterX; @@ -73,6 +76,12 @@ public class ExpansionConfig { /** 是否允许 Baritone 放置方块 */ public boolean allowPlace; + /** 寻路高度偏好:true 时寻路优先往高处走(y 越大启发成本越低/优先级越高,y 越小施惩罚) */ + public boolean altitudePreference = true; + + /** 高度偏好权重:每上升 1 格折算的启发成本减少量(越大越明显偏向高处,默认 0.1) */ + public double altitudeWeight = 0.1; + /** 是否启用避开怪物 */ public boolean avoidMobs = true; @@ -102,6 +111,7 @@ public class ExpansionConfig { c.retries = this.retries; c.returnThresholdSeconds = this.returnThresholdSeconds; c.returnWaitSeconds = this.returnWaitSeconds; + c.sideStepMaxRounds = this.sideStepMaxRounds; c.regionCenterX = this.regionCenterX; c.regionCenterZ = this.regionCenterZ; c.regionRadius = this.regionRadius; @@ -114,6 +124,8 @@ public class ExpansionConfig { c.allowJump = this.allowJump; c.allowBreak = this.allowBreak; c.allowPlace = this.allowPlace; + c.altitudePreference = this.altitudePreference; + c.altitudeWeight = this.altitudeWeight; c.avoidMobs = this.avoidMobs; c.autoEat = this.autoEat; c.eatThreshold = this.eatThreshold; diff --git a/src/main/java/autostore/client/controller/ExpansionController.java b/src/main/java/autostore/client/controller/ExpansionController.java index 28e3489..8897be5 100644 --- a/src/main/java/autostore/client/controller/ExpansionController.java +++ b/src/main/java/autostore/client/controller/ExpansionController.java @@ -100,8 +100,8 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { private long consecutiveTimeoutMillis; /** 发送 /u return 回城指令的时间戳(RETURNING 状态使用,用于等待传送完成) */ private long returnStartMillis; - /** 当前目标是否已使用过"往边上走两步"策略(重试耗尽时先用它,避免重复;再次耗尽才切换目标) */ - private boolean sideStepAttempted; + /** 当前目标已触发"往边上走两步"的次数(每次重试耗尽先走两步再试,达上限才切换目标/回城) */ + private int sideStepCount; /** 是否正在"往边上走两步"阶段(手动向目标连线垂直方向走开约 2.5 格再重试) */ private boolean sideStepping; /** 边走两步的方向符号(+1 左侧 / -1 右侧),被挡时换另一侧 */ @@ -229,7 +229,7 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { this.completedClaims = 0; this.retryCount = 0; this.consecutiveTimeoutMillis = 0L; - this.sideStepAttempted = false; + this.sideStepCount = 0; this.sideStepping = false; this.queue.clear(); this.claimedSet.clear(); @@ -369,8 +369,12 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { /** 让 Baritone 寻路前往当前目标区块,并进入 PATHING 状态 */ private void startPathing() { + // 寻路前先校验目标区块是否仍与己方领土接壤(地图异步刷新后可能已不接壤),不接壤则直接跳过 + if (this.skipIfTargetNotAdjacent()) { + return; + } this.retryCount = 0; - this.sideStepAttempted = false; + this.sideStepCount = 0; this.stageStartedMillis = System.currentTimeMillis(); this.lastProgress = 0; this.baritoneBridge.pathTo(this.target); @@ -398,8 +402,8 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { this.pathCalcFailed = false; ++this.retryCount; if (this.retryCount >= this.config.retries) { - // 重试耗尽:不直接切换目标,先"往边上走两步"再重试一次;仍失败才交给回城/切换处理 - if (!this.sideStepAttempted) { + // 重试耗尽:不直接切换目标/回城,先"往边上走两步"再重试原目标;可反复多轮,达上限才交给回城/切换 + if (this.sideStepCount < this.config.sideStepMaxRounds) { this.startSideStep(); } else { this.giveUpTarget("\u5bfb\u8def\u5931\u8d25\uff08\u76ee\u6807\u4e0d\u53ef\u8fbe\uff09"); @@ -436,11 +440,11 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { // 未耗尽:重新寻路(而非回城),取消旧路径并重新计算,往往"在边上走走"就能走出困境 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) { - // 重试耗尽:先不切换目标,用"往边上走两步"再重试一次(走开障碍后重新寻路原目标) + } else if (this.sideStepCount < this.config.sideStepMaxRounds) { + // 重试耗尽:先不切换目标/回城,用"往边上走两步"再重试一次(走开障碍后重新寻路原目标);可反复多轮 this.startSideStep(); } else { - // 走两步也未能脱困:连续超时达到阈值则回城传送重新开始,否则切换目标 + // 多轮走两步也未能脱困:连续超时达到阈值则回城传送重新开始,否则切换目标 this.giveUpTarget("\u5bfb\u8def\u8d85\u65f6"); } } @@ -486,19 +490,19 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { /** * 目标寻路重试耗尽时的脱困策略入口: * 取消当前寻路,改为手动朝"目标连线垂直方向"走开约 2.5 格(左右各试一侧), - * 走开后自动重新寻路原目标;走不开/超时则交给 {@link #giveUpTarget} 回城或切换目标。 - * 每个目标最多触发一次(sideStepAttempted 标志在 startPathing 时重置)。 + * 走开后自动重新寻路原目标;走不开/超时则再走两步或交给 {@link #giveUpTarget} 回城/切换目标。 + * 每个目标最多触发 {@code sideStepMaxRounds} 轮(sideStepCount 在 startPathing 时重置)。 */ private void startSideStep() { this.baritoneBridge.cancel(); MinecraftClient client = MinecraftClient.getInstance(); this.sideStepping = true; - this.sideStepAttempted = true; + ++this.sideStepCount; 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.log("\u5bfb\u8def\u91cd\u8bd5\u8017\u5c3d\uff0c\u5f00\u59cb\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65\u518d\u91cd\u8bd5\u539f\u76ee\u6807\uff08\u7b2c " + this.sideStepCount + "/" + this.config.sideStepMaxRounds + " \u8f6e\uff09"); this.setState(State.PATHING, "\u5f80\u8fb9\u4e0a\u8d70\u4e24\u6b65..."); } @@ -606,10 +610,13 @@ implements SubtitleChatMonitor.Listener, BaritoneBridge.PathListener { /** 重试当前目标:取消旧寻路,重新让 Baritone 前往同一目标区块 */ private void retrySameTarget(String statusText) { - // 目标已被他人占领(地图刷新后发现):不再重试,直接跳过,避免玩家被困在已占领区块 + // 重试前先校验目标有效性:已被他人占领或已不接壤时不再重试,直接跳过,避免玩家被困在无效区块 if (this.skipIfTargetClaimed("\u91cd\u8bd5\u65f6\u53d1\u73b0\u76ee\u6807\u5df2\u88ab\u5360\u9886")) { return; } + if (this.skipIfTargetNotAdjacent()) { + return; + } this.baritoneBridge.cancel(); this.stageStartedMillis = System.currentTimeMillis(); this.baritoneBridge.pathTo(this.target); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6c5fb4c..37f441b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1 +1 @@ -{"schemaVersion":1,"id":"autostore","version":"1.2.4","name":"AutoStore","description":"","authors":[],"contact":{},"license":"All-Rights-Reserved","environment":"client","entrypoints":{"fabric-datagen":["autostore.client.AutoStoreModDataGenerator"],"client":["autostore.client.AutoStoreModClient"],"main":["autostore.AutoStoreMod"]},"mixins":["autostore.mixins.json",{"config":"autostore.client.mixins.json","environment":"client"}],"depends":{"fabricloader":">=0.16.14","fabric-api":"*","minecraft":"1.21.4"},"jars":[{"file":"META-INF/jars/baritone-api-fabric-1.13.1.jar"}]} \ No newline at end of file +{"schemaVersion":1,"id":"autostore","version":"1.2.5","name":"AutoStore","description":"","authors":[],"contact":{},"license":"All-Rights-Reserved","environment":"client","entrypoints":{"fabric-datagen":["autostore.client.AutoStoreModDataGenerator"],"client":["autostore.client.AutoStoreModClient"],"main":["autostore.AutoStoreMod"]},"mixins":["autostore.mixins.json",{"config":"autostore.client.mixins.json","environment":"client"}],"depends":{"fabricloader":">=0.16.14","fabric-api":"*","minecraft":"1.21.4"},"jars":[{"file":"META-INF/jars/baritone-api-fabric-1.13.1.jar"}]} \ No newline at end of file