- 移除自动火把功能(按反馈不需要) - 主世界挖矿新增矿物绕道:每秒扫描周围10格目标矿物并自动绕道挖掉, 解决巷道壁旁钻石不挖的问题(此前只走巷道不主动找矿) - 新增工具适用性检查:用 isSuitableFor 校验手持工具能否采掘目标矿物, 远古残骸等自动换钻石镐;无合适工具时提示并暂停矿物挖掘 - 鱼骨支巷长度改为可配置 branchLength(默认10,原固定6) - 目标矿物 id 兼容 minecraft: 前缀(带不带均可识别)
354 行
14 KiB
Java
354 行
14 KiB
Java
package autodig.client.miner;
|
|
|
|
import autodig.client.bridge.BaritoneBridge;
|
|
import autodig.client.config.DigConfig;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import net.minecraft.block.BlockState;
|
|
import net.minecraft.block.Blocks;
|
|
import net.minecraft.client.MinecraftClient;
|
|
import net.minecraft.entity.player.PlayerEntity;
|
|
import net.minecraft.registry.Registries;
|
|
import net.minecraft.util.Identifier;
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
/**
|
|
* 主世界鱼骨巷道挖矿器。
|
|
*
|
|
* <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔
|
|
* {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。
|
|
* 推进实现为:把鱼骨图案转成一串目标坐标,按顺序派 Baritone 逐个前往;
|
|
* 因为允许挖方块,Baritone 会走直线把挡路的方块挖掉,从而挖出巷道。
|
|
* 自动切换工具由 Baritone 的 {@code autoTool} 负责。</p>
|
|
*
|
|
* <p>智能找矿:推进巷道的同时,周期性扫描玩家周围(默认 10 格)的目标矿物;
|
|
* 发现后临时绕道前往该矿物将其挖掉,再回到巷道计划继续推进。
|
|
* 这样巷道壁旁暴露的钻石等目标矿不会漏挖。</p>
|
|
*
|
|
* <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p>
|
|
*/
|
|
public final class OverworldMiner {
|
|
|
|
private final BaritoneBridge bridge;
|
|
|
|
private BlockPos start;
|
|
private int yLevel;
|
|
private List<BlockPos> plan = new ArrayList<>();
|
|
private int step;
|
|
/** 是否已完成 */
|
|
private boolean done;
|
|
/** 完成原因是否为「背包已满」 */
|
|
private boolean full;
|
|
/** 上一次朝向目标派发寻路的时间戳,用于失败重试节流 */
|
|
private long lastIssue;
|
|
/** 上一次打印诊断日志的时间戳,用于节流 */
|
|
private long lastLog;
|
|
/** 背包满连续持续的 tick 数(去抖:给自动丢垃圾留出腾位时间) */
|
|
private int fullTicks;
|
|
/** 矿物绕道目标;非 null 时优先前往挖掘该矿物 */
|
|
private BlockPos detourTarget;
|
|
/** 绕道已持续的 tick 数(超时放弃,避免卡死) */
|
|
private int detourTicks;
|
|
/** 上一次扫描附近矿物的时间戳(节流) */
|
|
private long lastOreScan;
|
|
/** 上次放弃的绕道点与时间(同一点短时间内不再重试,避免卡在不可达矿上) */
|
|
private BlockPos abandonedTarget;
|
|
private long abandonedAt;
|
|
|
|
/** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */
|
|
private static final int FULL_DEBOUNCE_TICKS = 20;
|
|
/** 矿物扫描间隔(毫秒) */
|
|
private static final long ORE_SCAN_INTERVAL_MS = 1000;
|
|
/** 矿物扫描半径(方块) */
|
|
private static final int ORE_SCAN_RADIUS = 10;
|
|
/** 单次绕道超时(tick):超过则放弃该矿点回到巷道计划 */
|
|
private static final int DETOUR_TIMEOUT_TICKS = 400;
|
|
/** 同一矿点放弃后的重试冷却(毫秒) */
|
|
private static final long ABANDON_COOLDOWN_MS = 30_000;
|
|
|
|
public OverworldMiner(BaritoneBridge bridge) {
|
|
this.bridge = bridge;
|
|
}
|
|
|
|
/**
|
|
* 以玩家当前位置为起点建立鱼骨巷道计划。
|
|
*
|
|
* @param config 挖矿配置
|
|
*/
|
|
public void init(DigConfig config) {
|
|
MinecraftClient client = MinecraftClient.getInstance();
|
|
PlayerEntity player = client.player;
|
|
if (player == null) {
|
|
return;
|
|
}
|
|
this.yLevel = config.overworldLevel;
|
|
BlockPos stop = player.getBlockPos();
|
|
this.start = new BlockPos(stop.getX(), this.yLevel, stop.getZ());
|
|
int branchLen = Math.max(1, config.branchLength);
|
|
int branchGap = 8;
|
|
// 用玩家水平朝向(而非移动方向)确定挖矿方向,站着不动时也稳定
|
|
net.minecraft.util.math.Direction facing = player.getHorizontalFacing();
|
|
int axis = facing.getAxis() == net.minecraft.util.math.Direction.Axis.X ? 0 : 1;
|
|
int sign = axis == 0 ? facing.getOffsetX() : facing.getOffsetZ();
|
|
this.plan = this.buildPlan(this.start, this.yLevel, config.tunnelLength, branchLen, branchGap, axis, sign);
|
|
this.step = 0;
|
|
this.done = false;
|
|
this.full = false;
|
|
this.fullTicks = 0;
|
|
this.detourTarget = null;
|
|
this.detourTicks = 0;
|
|
this.abandonedTarget = null;
|
|
this.abandonedAt = 0;
|
|
}
|
|
|
|
private List<BlockPos> buildPlan(BlockPos base, int y, int trunkLen, int branchLen, int branchGap, int axis, int sign) {
|
|
List<BlockPos> plan = new ArrayList<>();
|
|
int dx = axis == 0 ? sign : 0;
|
|
int dz = axis == 0 ? 0 : sign;
|
|
// 侧向单位向量
|
|
int sx = axis == 0 ? 0 : 1;
|
|
int sz = axis == 0 ? 1 : 0;
|
|
for (int t = 0; t <= trunkLen; t++) {
|
|
BlockPos trunk = base.add(dx * t, 0, dz * t);
|
|
plan.add(trunk);
|
|
// 每 branchGap 格向两侧各开一条侧支(去程+回程)
|
|
if (t > 0 && t % branchGap == 0) {
|
|
for (int side = -1; side <= 1; side += 2) {
|
|
for (int b = 1; b <= branchLen; b++) {
|
|
plan.add(trunk.add(sx * b * side, 0, sz * b * side));
|
|
}
|
|
for (int b = branchLen - 1; b >= 1; b--) {
|
|
plan.add(trunk.add(sx * b * side, 0, sz * b * side));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
/**
|
|
* 每帧推进一次。背包已满或走完全程则返回已完成。
|
|
*
|
|
* @param config 挖矿配置
|
|
* @param canMineOres 手持工具是否足以挖目标矿物(false 时跳过矿物绕道,避免白挖不掉落)
|
|
*/
|
|
public boolean tick(DigConfig config, boolean canMineOres) {
|
|
if (this.done) {
|
|
return true;
|
|
}
|
|
MinecraftClient client = MinecraftClient.getInstance();
|
|
PlayerEntity player = client.player;
|
|
if (player == null) {
|
|
return true;
|
|
}
|
|
// 背包满去抖:连续一段时间都满才认定为真满(给自动丢垃圾留出腾位时间)
|
|
if (this.inventoryFull(player)) {
|
|
this.fullTicks++;
|
|
if (this.fullTicks >= FULL_DEBOUNCE_TICKS) {
|
|
this.done = true;
|
|
this.full = true;
|
|
System.out.println("[AutoDig] overworld 结束:背包已满");
|
|
return true;
|
|
}
|
|
} else {
|
|
this.fullTicks = 0;
|
|
}
|
|
if (this.step >= this.plan.size()) {
|
|
this.done = true;
|
|
System.out.println("[AutoDig] overworld 结束:计划完成 step=" + this.step + " size=" + this.plan.size());
|
|
return true;
|
|
}
|
|
|
|
// ---- 矿物绕道挖掘:优先把附近暴露的目标矿物挖掉 ----
|
|
if (this.handleOreDetour(config, client, player, canMineOres)) {
|
|
return false; // 绕道期间不推进巷道
|
|
}
|
|
|
|
BlockPos target = this.plan.get(this.step);
|
|
double dist = this.distanceSq(player, target);
|
|
long now = System.currentTimeMillis();
|
|
if (now - this.lastLog > 5000) {
|
|
this.lastLog = now;
|
|
System.out.println("[AutoDig] overworld: step=" + this.step + "/" + this.plan.size()
|
|
+ " player=" + player.getBlockPos() + " target=" + target
|
|
+ " dist=" + String.format("%.1f", dist) + " active=" + this.bridge.isActive());
|
|
}
|
|
if (dist <= 2.0) {
|
|
// 已到达当前目标,推进到下一个
|
|
this.step++;
|
|
return this.step >= this.plan.size();
|
|
}
|
|
// 距离太长或 Baritone 没在干活时重新派发
|
|
if (dist > 4.0 || !this.bridge.isActive()) {
|
|
if (now - this.lastIssue > 250) {
|
|
this.bridge.gotoBlock(target);
|
|
this.lastIssue = now;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 处理矿物绕道挖掘。
|
|
*
|
|
* @return true 表示当前正在绕道挖矿(调用方应暂停巷道推进)
|
|
*/
|
|
private boolean handleOreDetour(DigConfig config, MinecraftClient client, PlayerEntity player, boolean canMineOres) {
|
|
long now = System.currentTimeMillis();
|
|
if (this.detourTarget != null) {
|
|
BlockState state = client.world.getBlockState(this.detourTarget);
|
|
if (!this.isTargetOre(config, state)) {
|
|
// 已挖掉或目标失效:回到巷道计划
|
|
this.detourTarget = null;
|
|
return false;
|
|
}
|
|
this.detourTicks++;
|
|
if (this.detourTicks > DETOUR_TIMEOUT_TICKS) {
|
|
// 太久没挖到(不可达等):放弃该点并冷却
|
|
System.out.println("[AutoDig] overworld 绕道超时,放弃矿点: " + this.detourTarget);
|
|
this.abandonedTarget = this.detourTarget;
|
|
this.abandonedAt = now;
|
|
this.detourTarget = null;
|
|
return false;
|
|
}
|
|
if (now - this.lastIssue > 250) {
|
|
this.bridge.gotoBlock(this.detourTarget);
|
|
this.lastIssue = now;
|
|
}
|
|
return true;
|
|
}
|
|
// 节流扫描附近目标矿物
|
|
if (!canMineOres || now - this.lastOreScan < ORE_SCAN_INTERVAL_MS) {
|
|
return false;
|
|
}
|
|
this.lastOreScan = now;
|
|
BlockPos ore = this.findNearbyOre(config, client, player);
|
|
if (ore != null) {
|
|
this.detourTarget = ore;
|
|
this.detourTicks = 0;
|
|
this.bridge.gotoBlock(ore);
|
|
this.lastIssue = now;
|
|
System.out.println("[AutoDig] overworld 发现目标矿物,绕道挖掘: " + ore);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** 在玩家周围扫描最近的目标矿物方块。 */
|
|
private BlockPos findNearbyOre(DigConfig config, MinecraftClient client, PlayerEntity player) {
|
|
Set<String> targets = this.targetOrePaths(config);
|
|
if (targets.isEmpty()) {
|
|
return null;
|
|
}
|
|
BlockPos best = null;
|
|
double bestDist = Double.MAX_VALUE;
|
|
for (BlockPos pos : BlockPos.iterateOutwards(player.getBlockPos(),
|
|
ORE_SCAN_RADIUS, ORE_SCAN_RADIUS, ORE_SCAN_RADIUS)) {
|
|
String path = Registries.BLOCK.getId(client.world.getBlockState(pos).getBlock()).getPath();
|
|
if (!targets.contains(path)) {
|
|
continue;
|
|
}
|
|
// 最近放弃过的矿点在冷却期内不再尝试
|
|
if (pos.equals(this.abandonedTarget)
|
|
&& System.currentTimeMillis() - this.abandonedAt < ABANDON_COOLDOWN_MS) {
|
|
continue;
|
|
}
|
|
double d = pos.getSquaredDistance(player.getBlockPos());
|
|
if (d < bestDist) {
|
|
bestDist = d;
|
|
best = pos.toImmutable();
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/** 判断方块状态是否为配置中的目标矿物(按 path 匹配,兼容命名空间前缀)。 */
|
|
private boolean isTargetOre(DigConfig config, BlockState state) {
|
|
if (state.isOf(Blocks.AIR)) {
|
|
return false;
|
|
}
|
|
String path = Registries.BLOCK.getId(state.getBlock()).getPath();
|
|
return this.targetOrePaths(config).contains(path);
|
|
}
|
|
|
|
/** 目标矿物 path 集合(去命名空间,兼容 "minecraft:" 前缀写法)。 */
|
|
private Set<String> targetOrePaths(DigConfig config) {
|
|
Set<String> set = new HashSet<>();
|
|
for (String ore : config.getOverworldOreList()) {
|
|
String path = ore.contains(":") ? ore.substring(ore.indexOf(':') + 1) : ore;
|
|
if (!path.isBlank()) {
|
|
set.add(path);
|
|
}
|
|
}
|
|
return set;
|
|
}
|
|
|
|
private double distanceSq(PlayerEntity player, BlockPos target) {
|
|
double dx = player.getX() - (target.getX() + 0.5);
|
|
double dy = player.getY() - target.getY();
|
|
double dz = player.getZ() - (target.getZ() + 0.5);
|
|
return dx * dx + dy * dy + dz * dz;
|
|
}
|
|
|
|
private boolean inventoryFull(PlayerEntity player) {
|
|
for (int i = 0; i < 36; i++) {
|
|
if (player.getInventory().main.get(i).isEmpty()) {
|
|
return false;
|
|
}
|
|
}
|
|
// 主背包全满(含快捷栏),无空位可存放
|
|
return true;
|
|
}
|
|
|
|
/** @return 是否已挖完 */
|
|
public boolean isDone() {
|
|
return this.done;
|
|
}
|
|
|
|
/** @return 当前推进到的计划步数(从 0 开始) */
|
|
public int currentStep() {
|
|
return this.step;
|
|
}
|
|
|
|
/** @return 鱼骨计划总步数 */
|
|
public int totalSteps() {
|
|
return this.plan.size();
|
|
}
|
|
|
|
/** @return 完成原因是否为「背包已满」 */
|
|
public boolean isFull() {
|
|
return this.full;
|
|
}
|
|
|
|
/**
|
|
* 恢复挖掘(死亡重生/保命恢复后复用):已有未完成计划时复用原计划,
|
|
* 把指针回退到离玩家最近的目标点;否则按当前位置重新建立计划。
|
|
*
|
|
* @param config 挖矿配置
|
|
*/
|
|
public void continueOrInit(DigConfig config) {
|
|
MinecraftClient client = MinecraftClient.getInstance();
|
|
PlayerEntity player = client.player;
|
|
if (this.plan.isEmpty() || this.done || player == null) {
|
|
this.init(config);
|
|
return;
|
|
}
|
|
int from = Math.max(0, this.step - 8);
|
|
int to = Math.min(this.plan.size() - 1, this.step + 8);
|
|
int best = this.step;
|
|
double bestDist = Double.MAX_VALUE;
|
|
for (int i = from; i <= to; i++) {
|
|
double d = this.distanceSq(player, this.plan.get(i));
|
|
if (d < bestDist) {
|
|
bestDist = d;
|
|
best = i;
|
|
}
|
|
}
|
|
this.step = best;
|
|
this.done = false;
|
|
this.full = false;
|
|
this.fullTicks = 0;
|
|
}
|
|
} |