首次提交 AutoDig Mod

这个提交包含在:
lyizai
2026-08-20 21:04:27 +08:00
当前提交 95c4e97db0
共修改 26 个文件,包含 1851 行新增和 0 行删除
@@ -0,0 +1,235 @@
package autodig.client.controller;
import autodig.client.bridge.BaritoneBridge;
import autodig.client.config.DigConfig;
import autodig.client.config.MineTask;
import autodig.client.miner.DebrisHandler;
import autodig.client.miner.FishboneMiner;
import autodig.client.service.AutoEat;
import autodig.client.service.DeathRecorder;
import autodig.client.service.KillAura;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.Heightmap;
/**
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录和各任务
* 专用挖矿器串起来,驱动一个简单的状态机。
*/
public final class DigController {
/** 内部状态机 */
private enum State {
IDLE, FISHBONE, CLIMB, DEBRIS, FLEE, RETURN_TO_DEATH
}
private final BaritoneBridge bridge = new BaritoneBridge();
private final AutoEat autoEat = new AutoEat();
private final KillAura killAura = new KillAura();
private final DeathRecorder deathRecorder = new DeathRecorder();
private final FishboneMiner fishbone = new FishboneMiner(this.bridge);
private final DebrisHandler debris = new DebrisHandler(this.bridge);
private State state = State.IDLE;
private BlockPos resumeTarget;
private long lastIssue;
private boolean wasDead;
/** 每帧驱动整个自动挖矿流程。 */
public void tick(DigConfig config) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.player == null || client.world == null) {
this.state = State.IDLE;
return;
}
PlayerEntity player = client.player;
// 常驻能力:杀戮光环、自动进食
this.killAura.tick(config);
this.autoEat.tick(config);
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
this.deathRecorder.tick(config, this::onRespawn);
if (player.isDead()) {
this.wasDead = true;
this.bridge.cancelAll();
return;
}
this.wasDead = false;
// 保命优先:血线过低时向上撤离(不冒进挖矿)
if (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
this.fleeTick(config);
return;
}
// 未选择任务时归位
if (config.task == MineTask.NONE) {
if (this.state != State.IDLE) {
this.bridge.cancelAll();
this.state = State.IDLE;
}
return;
}
this.drive(config, player);
}
private void drive(DigConfig config, PlayerEntity player) {
switch (this.state) {
case IDLE -> {
if (config.task == MineTask.DIAMOND) {
this.fishbone.init(config);
this.state = State.FISHBONE;
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
this.debris.reset();
this.state = State.DEBRIS;
}
}
case FISHBONE -> {
player.sendMessage(Text.literal("AutoDig: 主世界鱼骨挖矿进行中"), true);
if (this.fishbone.tick(config)) {
this.bridge.cancelAll();
this.pathToSurface(player.getBlockPos());
this.state = State.CLIMB;
}
}
case CLIMB -> {
if (this.nearSurface(player)) {
this.finish("鱼骨挖矿完成,已返回地表");
}
}
case DEBRIS -> {
player.sendMessage(Text.literal("AutoDig: 下界远古残骸挖矿进行中"), true);
if (this.debris.tick(config)) {
this.finish("远古残骸任务完成,已回到主世界");
}
}
case RETURN_TO_DEATH -> this.returnToDeathTick(player, config);
case FLEE -> {
// FLEE 在 tick() 中单独处理,正常情况下不会进到这里
}
}
}
/** 重生后先回到最近的死亡坐标,再续上原任务。 */
private void onRespawn() {
BlockPos deathPos = this.deathRecorder.lastDeathPos();
if (deathPos != null) {
this.bridge.cancelAll();
this.resumeTarget = deathPos;
this.state = State.RETURN_TO_DEATH;
}
}
private void returnToDeathTick(PlayerEntity player, DigConfig config) {
if (this.resumeTarget == null) {
this.resumeTask(config);
return;
}
double dist = this.distanceSq(player, this.resumeTarget);
if (dist < 9.0) {
this.resumeTarget = null;
this.resumeTask(config);
} else {
this.issue(() -> this.bridge.gotoBlock(this.resumeTarget));
}
}
/** 按当前任务与所在维度恢复挖矿。 */
private void resumeTask(DigConfig config) {
MinecraftClient client = MinecraftClient.getInstance();
boolean inNether = client.world != null && client.world.getRegistryKey() == net.minecraft.world.World.NETHER;
if (config.task == MineTask.DIAMOND) {
this.fishbone.init(config);
this.state = State.FISHBONE;
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
this.debris.reset();
this.state = State.DEBRIS;
// 若已在下界,让残留处理机直接从挖矿阶段继续
if (inNether) {
this.state = State.DEBRIS;
}
} else {
this.state = State.IDLE;
}
}
/** 保命撤离:把玩家送到脚下地表,血线恢复后继续原任务。 */
private void fleeTick(DigConfig config) {
MinecraftClient client = MinecraftClient.getInstance();
PlayerEntity player = client.player;
if (player == null) {
return;
}
this.bridge.cancelAll();
this.pathToSurface(player.getBlockPos());
if (this.healthPercent(player) >= config.healthFleeThreshold + 20) {
this.resumeTask(config);
}
}
private void finish(String message) {
this.bridge.cancelAll();
MinecraftClient client = MinecraftClient.getInstance();
if (client.player != null) {
client.player.sendMessage(Text.literal("AutoDig: " + message), true);
}
this.state = State.IDLE;
}
private double healthPercent(PlayerEntity player) {
return player.getHealth() / Math.max(player.getMaxHealth(), 1.0) * 100.0;
}
private double distanceSq(PlayerEntity player, BlockPos target) {
double dx = player.getX() - (target.getX() + 0.5);
double dz = player.getZ() - (target.getZ() + 0.5);
return dx * dx + dz * dz;
}
private void issue(Runnable action) {
long now = System.currentTimeMillis();
if (now - this.lastIssue > 250) {
action.run();
this.lastIssue = now;
}
}
/** 计算垂直方向上的地表坐标并派发前往(节流)。 */
private void pathToSurface(BlockPos below) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) {
return;
}
int x = below.getX();
int z = below.getZ();
int top = client.world.getTopY(Heightmap.Type.MOTION_BLOCKING, x, z);
BlockPos surface = new BlockPos(x, Math.max(top, below.getY()), z);
this.issue(() -> this.bridge.gotoBlock(surface));
}
/** 玩家是否已回到地表。 */
private boolean nearSurface(PlayerEntity player) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) {
return true;
}
int x = player.getBlockX();
int z = player.getBlockZ();
int top = client.world.getTopY(Heightmap.Type.MOTION_BLOCKING, x, z);
return Math.abs(player.getY() - (top + 1)) < 4.0;
}
/** @return Baritone 桥接(供其他模块复用)。 */
public BaritoneBridge bridge() {
return this.bridge;
}
/** 供 GUI/调试获取当前状态文本。 */
public String statusText() {
return this.state.name();
}
}