下界不再用 Baritone mineByName 自动找矿(服务器反作弊隐藏矿物后扫描失效、到处乱走),改为主动巷道推进:在 netherLevel 层(默认15)挖直巷,逐点 gotoBlock 推进,主动挖方块暴露远古残骸;巷道挖完未挖够时旋转90°转移新区域继续挖。新增 GUI 设置:挖掘层Y、巷道长度。修复空缺闲铜铁镐不挖远古残骸的 bug。
592 行
24 KiB
Java
592 行
24 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.Map;
|
||
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.item.ItemStack;
|
||
import net.minecraft.registry.Registries;
|
||
import net.minecraft.registry.RegistryKey;
|
||
import net.minecraft.util.math.BlockPos;
|
||
import net.minecraft.util.math.Direction;
|
||
import net.minecraft.world.World;
|
||
|
||
/**
|
||
* 下界自动挖矿流转。
|
||
*
|
||
* <p>流程:主世界 -> 走进最近传送门 -> 进入下界 -> 挖可配置矿物 ->
|
||
* (挖够数量/背包满/血量低/装备耐久低)-> 找下界传送门走回主世界。
|
||
* 死亡重生的返回逻辑由控制器统一处理,本类每次 tick 依据当前所在维度
|
||
* 自适应地执行对应阶段。</p>
|
||
*
|
||
* <p>挖矿方式:与主世界相同采用<b>巷道扫描式</b>——在配置的 Y 层
|
||
* (远古残骸富集层,默认 15)沿玩家朝向挖直巷,逐点推进;巷道壁两侧
|
||
* 暴露的目标矿物由周期性扫描绕道挖掉。这样<b>不依赖 Baritone 的矿物
|
||
* 远距离扫描</b>,即使服务器反作弊隐藏矿物方块,主动挖巷道也能把路径
|
||
* 上的矿物挖出来。一条巷道挖完仍未挖够目标数量时,方向旋转 90° 转移
|
||
* 到新区域继续挖,避免漫无目的地乱走。</p>
|
||
*
|
||
* <p>下界无死亡不掉落,比主世界更"惜命":血量低或装备耐久低时主动触发返程。
|
||
* 背包满采用去抖判定(连续 1 秒都满才算真满),给自动丢垃圾服务留出腾位时间。</p>
|
||
*/
|
||
public final class NetherMiner {
|
||
|
||
private final BaritoneBridge bridge;
|
||
|
||
private enum Stage {
|
||
/** 在下界从传送门前往主世界(或已在主世界即完成) */
|
||
GO_COMMIT,
|
||
/** 在前一个维度前往传送门 */
|
||
GO_PORTAL,
|
||
/** 挖矿 */
|
||
MINE,
|
||
/** 返回主世界 */
|
||
RETURN_PORTAL
|
||
}
|
||
|
||
/** 背包满去抖阈值(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 = 6;
|
||
/** 单次绕道超时(tick):超过则放弃该矿点回到巷道计划 */
|
||
private static final int DETOUR_TIMEOUT_TICKS = 400;
|
||
/** 同一矿点放弃后的重试冷却(毫秒) */
|
||
private static final long ABANDON_COOLDOWN_MS = 30_000;
|
||
/** 巷道目标点不可达超时(毫秒):超过此时间仍未接近,放弃该点跳到下一个 */
|
||
private static final long WAYPOINT_STUCK_MS = 20_000;
|
||
/** 转移寻路超时(毫秒):超过则放弃转移,原地重建巷道 */
|
||
private static final long RELOCATE_TIMEOUT_MS = 15_000;
|
||
/** 转移距离(方块) */
|
||
private static final int RELOCATE_DISTANCE = 12;
|
||
|
||
/** 常见矿物方块 id -> 掉落物品 id(挖掘产物与方块本身不同的情况) */
|
||
private static final Map<String, String> ORE_TO_DROP = Map.ofEntries(
|
||
Map.entry("diamond_ore", "diamond"),
|
||
Map.entry("deepslate_diamond_ore", "diamond"),
|
||
Map.entry("emerald_ore", "emerald"),
|
||
Map.entry("deepslate_emerald_ore", "emerald"),
|
||
Map.entry("lapis_ore", "lapis_lazuli"),
|
||
Map.entry("deepslate_lapis_ore", "lapis_lazuli"),
|
||
Map.entry("redstone_ore", "redstone"),
|
||
Map.entry("deepslate_redstone_ore", "redstone"),
|
||
Map.entry("coal_ore", "coal"),
|
||
Map.entry("deepslate_coal_ore", "coal"),
|
||
Map.entry("copper_ore", "raw_copper"),
|
||
Map.entry("deepslate_copper_ore", "raw_copper"),
|
||
Map.entry("iron_ore", "raw_iron"),
|
||
Map.entry("deepslate_iron_ore", "raw_iron"),
|
||
Map.entry("gold_ore", "raw_gold"),
|
||
Map.entry("deepslate_gold_ore", "raw_gold"),
|
||
Map.entry("nether_gold_ore", "gold_nugget"),
|
||
Map.entry("nether_quartz_ore", "quartz"));
|
||
// ancient_debris 挖掉落后就是 ancient_debris 物品,走默认映射
|
||
|
||
private Stage stage = Stage.GO_COMMIT;
|
||
private boolean done;
|
||
/** 结束原因是否为「背包已满」 */
|
||
private boolean full;
|
||
/** 结束原因是否为「血量过低触发返程」 */
|
||
private boolean fled;
|
||
/** 结束原因是否为「装备耐久过低触发返程」 */
|
||
private boolean gearBroken;
|
||
/** 外部(控制器)要求的强制返程 */
|
||
private boolean forceReturn;
|
||
/** 派发节流 */
|
||
private long lastIssue;
|
||
/** 已挖到的目标矿物数量(按背包产物计数,扣除任务开始时的基线) */
|
||
private int minedCount;
|
||
/** 任务开始时背包里已有的目标产物数量(避免老库存被误计为战果) */
|
||
private int baselineCount;
|
||
private boolean baselineSet;
|
||
/** 背包满连续持续的 tick 数(去抖) */
|
||
private int fullTicks;
|
||
|
||
// ---- 巷道推进状态 ----
|
||
/** 下界巷道计划(直巷,y = config.netherLevel) */
|
||
private List<BlockPos> plan = new ArrayList<>();
|
||
/** 当前推进到的计划步数 */
|
||
private int step;
|
||
/** 当前巷道方向 */
|
||
private int dirX;
|
||
private int dirZ;
|
||
/** 上一次打印诊断日志的时间戳(节流) */
|
||
private long lastLog;
|
||
/** 上一次成功下发的目标点(避免同一目标反复重发) */
|
||
private BlockPos lastIssuedTarget;
|
||
/** 当前巷道目标点的最近进度时间戳与最佳距离(用于超时/不可达放弃) */
|
||
private long lastProgress;
|
||
private double bestDist;
|
||
|
||
// ---- 矿物绕道状态 ----
|
||
/** 绕道目标;非 null 时优先前往挖掘该矿物 */
|
||
private BlockPos detourTarget;
|
||
/** 绕道已持续的 tick 数(超时放弃,避免卡死) */
|
||
private int detourTicks;
|
||
/** 上一次扫描附近矿物的时间戳(节流) */
|
||
private long lastOreScan;
|
||
/** 上次放弃的绕道点与时间(同一点短时间内不再重试) */
|
||
private BlockPos abandonedTarget;
|
||
private long abandonedAt;
|
||
|
||
// ---- 转移新区域状态 ----
|
||
/** 是否正在转移到新矿区 */
|
||
private boolean relocating;
|
||
/** 转移目标点 */
|
||
private BlockPos relocateTarget;
|
||
/** 转移开始时间戳 */
|
||
private long relocateStart;
|
||
|
||
public NetherMiner(BaritoneBridge bridge) {
|
||
this.bridge = bridge;
|
||
}
|
||
|
||
/** 任务结束或需要切换任务时归还控制权。 */
|
||
public void reset() {
|
||
this.stage = Stage.GO_COMMIT;
|
||
this.done = false;
|
||
this.full = false;
|
||
this.fled = false;
|
||
this.gearBroken = false;
|
||
this.forceReturn = false;
|
||
this.minedCount = 0;
|
||
this.baselineCount = 0;
|
||
this.baselineSet = false;
|
||
this.fullTicks = 0;
|
||
this.plan.clear();
|
||
this.step = 0;
|
||
this.lastIssuedTarget = null;
|
||
this.lastProgress = System.currentTimeMillis();
|
||
this.bestDist = Double.MAX_VALUE;
|
||
this.detourTarget = null;
|
||
this.detourTicks = 0;
|
||
this.abandonedTarget = null;
|
||
this.abandonedAt = 0;
|
||
this.relocating = false;
|
||
this.relocateTarget = null;
|
||
this.relocateStart = 0;
|
||
}
|
||
|
||
/**
|
||
* 每帧推进下界挖矿流转。
|
||
*
|
||
* @param config 挖矿配置
|
||
* @param canMineOres 手持工具是否足以挖目标矿物(false 时跳过矿物绕道,避免白挖不掉落)
|
||
* @return 是否已全部完成(含回到主世界)
|
||
*/
|
||
public boolean tick(DigConfig config, boolean canMineOres) {
|
||
MinecraftClient client = MinecraftClient.getInstance();
|
||
if (client.player == null || client.world == null) {
|
||
return this.done;
|
||
}
|
||
PlayerEntity player = client.player;
|
||
RegistryKey<World> dim = client.world.getRegistryKey();
|
||
boolean inOverworld = dim == World.OVERWORLD;
|
||
boolean inNether = dim == World.NETHER;
|
||
|
||
// 下界惜命:血量过低时触发返程(只触发一次,避免反复取消回程寻路)
|
||
if (inNether && this.stage != Stage.RETURN_PORTAL && !this.done
|
||
&& config.netherFleeHealthThreshold > 0
|
||
&& this.healthPercent(player) < config.netherFleeHealthThreshold) {
|
||
this.fled = true;
|
||
this.bridge.cancelAll();
|
||
if (config.netherAutoReturn) {
|
||
this.stage = Stage.RETURN_PORTAL;
|
||
} else {
|
||
this.done = true;
|
||
}
|
||
return this.done;
|
||
}
|
||
|
||
// 外部标记(装备耐久过低等):直接进入返程
|
||
if (this.forceReturn && inNether && this.stage != Stage.RETURN_PORTAL) {
|
||
this.bridge.cancelAll();
|
||
this.stage = Stage.RETURN_PORTAL;
|
||
}
|
||
|
||
switch (this.stage) {
|
||
case GO_COMMIT -> {
|
||
// 选择去哪个维度
|
||
if (inNether) {
|
||
this.stage = Stage.MINE;
|
||
} else {
|
||
this.stage = Stage.GO_PORTAL;
|
||
}
|
||
}
|
||
case GO_PORTAL -> {
|
||
if (inNether) {
|
||
this.stage = Stage.MINE; // 已通过传送门进入下界
|
||
} else {
|
||
this.issue(() -> this.bridge.toNetherPortal());
|
||
}
|
||
}
|
||
case MINE -> {
|
||
if (!inNether) {
|
||
// 意外回到主世界,重走流程
|
||
this.stage = Stage.GO_COMMIT;
|
||
return this.done;
|
||
}
|
||
// 记录任务开始时背包已有的产物数量作为基线
|
||
if (!this.baselineSet) {
|
||
this.baselineCount = this.countProducts(config, player);
|
||
this.baselineSet = true;
|
||
}
|
||
// 背包满去抖:连续一段时间都满才认定为真满(给自动丢垃圾留时间)
|
||
if (this.inventoryFull(player)) {
|
||
this.fullTicks++;
|
||
} else {
|
||
this.fullTicks = 0;
|
||
}
|
||
if (this.fullTicks >= FULL_DEBOUNCE_TICKS) {
|
||
// 背包已满:期望回程则去传送门;不回程时无处可放,直接结束
|
||
this.full = true;
|
||
if (config.netherAutoReturn) {
|
||
this.stage = Stage.RETURN_PORTAL;
|
||
} else {
|
||
this.done = true;
|
||
}
|
||
} else if (this.minedEnough(config, player)) {
|
||
// 已挖够目标数量
|
||
this.bridge.cancelAll();
|
||
if (config.netherAutoReturn) {
|
||
this.stage = Stage.RETURN_PORTAL;
|
||
} else {
|
||
this.done = true;
|
||
}
|
||
} else {
|
||
// 未挖够目标数量:巷道推进挖矿,绝不因 Baritone 空闲误判"任务完成"
|
||
this.mineTick(config, player, canMineOres);
|
||
}
|
||
}
|
||
case RETURN_PORTAL -> {
|
||
if (inOverworld) {
|
||
this.done = true; // 已回到主世界,全流程完成
|
||
} else {
|
||
this.issue(() -> this.bridge.toNetherPortal());
|
||
}
|
||
}
|
||
}
|
||
return this.done;
|
||
}
|
||
|
||
/** 判断是否已挖够目标矿物数量(按背包产物计数,扣除任务开始时的基线)。 */
|
||
private boolean minedEnough(DigConfig config, PlayerEntity player) {
|
||
this.minedCount = this.countProducts(config, player) - this.baselineCount;
|
||
if (config.netherTargetCount <= 0) {
|
||
return false; // -1 或 0 表示无限挖
|
||
}
|
||
return this.minedCount >= config.netherTargetCount;
|
||
}
|
||
|
||
/**
|
||
* 巷道扫描式挖矿:建立/推进直巷计划,途中绕道挖掉暴露的目标矿物。
|
||
*/
|
||
private void mineTick(DigConfig config, PlayerEntity player, boolean canMineOres) {
|
||
MinecraftClient client = MinecraftClient.getInstance();
|
||
if (client.world == null) {
|
||
return;
|
||
}
|
||
// 尚无巷道计划或刚转移完成:建立新计划
|
||
if (this.plan.isEmpty()) {
|
||
this.initTunnel(config, player);
|
||
}
|
||
if (this.relocating) {
|
||
double d = this.distanceSq(player, this.relocateTarget);
|
||
if (d <= 4.0) {
|
||
// 已到新矿区,重建巷道
|
||
this.relocating = false;
|
||
this.relocateTarget = null;
|
||
this.plan.clear();
|
||
} else if (System.currentTimeMillis() - this.relocateStart > RELOCATE_TIMEOUT_MS) {
|
||
// 转移超时(目标点不可达):放弃转移,原地重建巷道
|
||
this.relocating = false;
|
||
this.relocateTarget = null;
|
||
this.bridge.cancelAll();
|
||
this.plan.clear();
|
||
} else if (!this.bridge.isActive()) {
|
||
this.bridge.gotoBlock(this.relocateTarget);
|
||
}
|
||
return;
|
||
}
|
||
// 先处理矿物绕道(巷道壁暴露的目标矿物优先挖掉)
|
||
if (this.handleOreDetour(config, player, canMineOres)) {
|
||
return;
|
||
}
|
||
// 巷道走完:方向旋转 90° 转移到新区域继续挖
|
||
if (this.step >= this.plan.size()) {
|
||
this.rotateAndRelocate(player);
|
||
return;
|
||
}
|
||
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] nether: 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++;
|
||
this.lastIssuedTarget = null;
|
||
this.lastProgress = now;
|
||
this.bestDist = Double.MAX_VALUE;
|
||
return;
|
||
}
|
||
// 进度跟踪:只要在接近目标就刷新"最近有进展"时间
|
||
if (dist < this.bestDist) {
|
||
this.bestDist = dist;
|
||
this.lastProgress = now;
|
||
}
|
||
// 长时间无进展(目标不可达)→ 放弃该点,跳到下一个
|
||
if (now - this.lastProgress > WAYPOINT_STUCK_MS) {
|
||
System.out.println("[AutoDig] nether 目标长时间不可达,跳过 step=" + this.step + " target=" + target);
|
||
this.bridge.cancelAll();
|
||
this.step++;
|
||
this.lastIssuedTarget = null;
|
||
this.lastProgress = now;
|
||
this.bestDist = Double.MAX_VALUE;
|
||
return;
|
||
}
|
||
// 下发寻路:换了新目标才立即下发;同一目标仅在 Baritone 空闲时低频重试
|
||
if (!target.equals(this.lastIssuedTarget)) {
|
||
this.bridge.gotoBlock(target);
|
||
this.lastIssuedTarget = target;
|
||
this.lastIssue = now;
|
||
this.lastProgress = now;
|
||
this.bestDist = Double.MAX_VALUE;
|
||
} else if (!this.bridge.isActive() && now - this.lastIssue > 2000) {
|
||
this.bridge.gotoBlock(target);
|
||
this.lastIssue = now;
|
||
}
|
||
}
|
||
|
||
/** 建立一条直巷计划:以玩家水平坐标为起点,y = config.netherLevel,沿当前朝向推进。 */
|
||
private void initTunnel(DigConfig config, PlayerEntity player) {
|
||
Direction facing = player.getHorizontalFacing();
|
||
int axis = facing.getAxis() == Direction.Axis.X ? 0 : 1;
|
||
int sign = axis == 0 ? facing.getOffsetX() : facing.getOffsetZ();
|
||
this.dirX = axis == 0 ? sign : 0;
|
||
this.dirZ = axis == 0 ? 0 : sign;
|
||
BlockPos start = new BlockPos(player.getBlockPos().getX(), config.netherLevel, player.getBlockPos().getZ());
|
||
this.plan = new ArrayList<>();
|
||
for (int t = 0; t <= config.netherTunnelLength; t++) {
|
||
this.plan.add(start.add(this.dirX * t, 0, this.dirZ * t));
|
||
}
|
||
this.step = 0;
|
||
this.lastIssuedTarget = null;
|
||
this.lastProgress = System.currentTimeMillis();
|
||
this.bestDist = Double.MAX_VALUE;
|
||
System.out.println("[AutoDig] nether 巷道建立: y=" + config.netherLevel
|
||
+ " 方向=(" + this.dirX + "," + this.dirZ + ") 长度=" + config.netherTunnelLength);
|
||
}
|
||
|
||
/** 巷道挖完仍未挖够:方向旋转 90°,转移到侧方新区域继续挖。 */
|
||
private void rotateAndRelocate(PlayerEntity player) {
|
||
int nx = -this.dirZ;
|
||
int nz = this.dirX;
|
||
this.dirX = nx;
|
||
this.dirZ = nz;
|
||
this.relocateTarget = player.getBlockPos().add(this.dirX * RELOCATE_DISTANCE, 0, this.dirZ * RELOCATE_DISTANCE);
|
||
this.relocateStart = System.currentTimeMillis();
|
||
this.relocating = true;
|
||
this.plan.clear();
|
||
this.bridge.gotoBlock(this.relocateTarget);
|
||
System.out.println("[AutoDig] nether 巷道挖完,转移到新区域: " + this.relocateTarget);
|
||
}
|
||
|
||
/**
|
||
* 处理矿物绕道挖掘。
|
||
*
|
||
* @return true 表示当前正在绕道挖矿(调用方应暂停巷道推进)
|
||
*/
|
||
private boolean handleOreDetour(DigConfig config, PlayerEntity player, boolean canMineOres) {
|
||
MinecraftClient client = MinecraftClient.getInstance();
|
||
if (client.world == null) {
|
||
return false;
|
||
}
|
||
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) {
|
||
// 太久没挖到(不可达等):放弃该点并冷却
|
||
this.abandonedTarget = this.detourTarget;
|
||
this.abandonedAt = now;
|
||
this.detourTarget = null;
|
||
return false;
|
||
}
|
||
// 仅在 Baritone 空闲时低频重发,避免打断正在规划的路线
|
||
if (!this.bridge.isActive() && now - this.lastIssue > 1000) {
|
||
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] nether 发现目标矿物,绕道挖掘: " + 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.getNetherOreList()) {
|
||
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 dz = player.getZ() - (target.getZ() + 0.5);
|
||
double dy = player.getY() - (target.getY() + 0.5);
|
||
return dx * dx + dy * dy + dz * dz;
|
||
}
|
||
|
||
/** 统计背包中目标矿物产物的总数量(方块 id 映射到掉落物 id)。 */
|
||
private int countProducts(DigConfig config, PlayerEntity player) {
|
||
List<String> ores = config.getNetherOreList();
|
||
if (ores.isEmpty()) {
|
||
return 0;
|
||
}
|
||
Set<String> products = new HashSet<>();
|
||
for (String ore : ores) {
|
||
products.add(ORE_TO_DROP.getOrDefault(ore, ore));
|
||
}
|
||
int total = 0;
|
||
for (int i = 0; i < 36; i++) {
|
||
ItemStack stack = player.getInventory().main.get(i);
|
||
if (stack.isEmpty()) {
|
||
continue;
|
||
}
|
||
if (products.contains(itemId(stack))) {
|
||
total += stack.getCount();
|
||
}
|
||
}
|
||
return total;
|
||
}
|
||
|
||
private static String itemId(ItemStack stack) {
|
||
return Registries.ITEM.getId(stack.getItem()).toString();
|
||
}
|
||
|
||
private void issue(Runnable action) {
|
||
long now = System.currentTimeMillis();
|
||
if (now - this.lastIssue > 1000) {
|
||
action.run();
|
||
this.lastIssue = now;
|
||
}
|
||
}
|
||
|
||
private boolean inventoryFull(PlayerEntity player) {
|
||
for (int i = 0; i < 36; i++) {
|
||
if (player.getInventory().main.get(i).isEmpty()) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private double healthPercent(PlayerEntity player) {
|
||
return player.getHealth() / Math.max(player.getMaxHealth(), 1.0) * 100.0;
|
||
}
|
||
|
||
public boolean isDone() {
|
||
return this.done;
|
||
}
|
||
|
||
/** @return 结束原因是否为「背包已满」 */
|
||
public boolean isFull() {
|
||
return this.full;
|
||
}
|
||
|
||
/** @return 结束原因是否为「血量过低触发返程」 */
|
||
public boolean isFled() {
|
||
return this.fled;
|
||
}
|
||
|
||
/** @return 结束原因是否为「装备耐久过低触发返程」 */
|
||
public boolean isGearBroken() {
|
||
return this.gearBroken;
|
||
}
|
||
|
||
/** 标记装备耐久过低,触发强制返程 */
|
||
public void markGearBroken() {
|
||
this.gearBroken = true;
|
||
this.forceReturn = true;
|
||
this.bridge.cancelAll();
|
||
}
|
||
|
||
/** @return 本次任务新挖到的目标矿物数量 */
|
||
public int getMinedCount() {
|
||
return this.minedCount;
|
||
}
|
||
}
|