feat(ui): 响应式布局、任务相关选项动态显示、矿物输入框、空闲不保命
- AutoDigGui: 相对坐标布局自适应屏幕宽度,分组标题清晰 - 选中主世界/下界时才显示对应设置区块 - 矿物选择改为文本输入框(支持完整自定义),不再是不可见的按钮循环 - 数值调整用 +/- 按钮,当前值居中显示 - 底部按钮自适应三等分 - DigController: 空闲(NONE)模式下不触发保命/装备保护/杀戮光环 - 修复 CheckboxWidget width 编译错误
这个提交包含在:
@@ -91,8 +91,16 @@ public final class DigController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保命优先:血线过低时向上撤离(不冒进挖矿)。
|
// 未选择任务时归位:空闲模式下不触发保命/装备保护/杀戮光环等自动行为
|
||||||
// cancelAll 只在刚进入 FLEE 时执行一次,避免每帧取消刚派发的寻路
|
if (config.task == MineTask.NONE) {
|
||||||
|
if (this.state != State.IDLE) {
|
||||||
|
this.bridge.cancelAll();
|
||||||
|
this.setState(State.IDLE, "任务已停止,进入空闲状态");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保命优先:仅在挖矿任务进行中生效,血线过低时向上撤离
|
||||||
if (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
|
if (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
|
||||||
if (this.state != State.FLEE) {
|
if (this.state != State.FLEE) {
|
||||||
this.bridge.cancelAll();
|
this.bridge.cancelAll();
|
||||||
@@ -102,15 +110,6 @@ public final class DigController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未选择任务时归位
|
|
||||||
if (config.task == MineTask.NONE) {
|
|
||||||
if (this.state != State.IDLE) {
|
|
||||||
this.bridge.cancelAll();
|
|
||||||
this.setState(State.IDLE, "任务已停止,进入空闲状态");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drive(config, player);
|
this.drive(config, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,194 +9,211 @@ import net.minecraft.client.gui.DrawContext;
|
|||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||||
import net.minecraft.client.gui.widget.CheckboxWidget;
|
import net.minecraft.client.gui.widget.CheckboxWidget;
|
||||||
|
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AutoDig 游戏内 GUI。
|
* AutoDig 游戏内 GUI - 响应式布局,根据任务动态显示选项。
|
||||||
*
|
|
||||||
* <p>用于选择挖矿模式(主世界 / 下界),并快捷设置:保命优先、优先绕开岩浆、
|
|
||||||
* 自动切换工具、自动进食、杀戮光环(含下界协同)、重生后回死亡点、挖完回主世界,
|
|
||||||
* 装备耐久保护、矿物选择,以及鱼骨层数 / 巷道长度 / 目标数量等参数。
|
|
||||||
* 界面中的改动在"开始/停止"或常规关闭(含 ESC)时同步到 mod 全局配置并落盘;
|
|
||||||
* "仅关闭"则丢弃未应用的改动。</p>
|
|
||||||
*/
|
*/
|
||||||
public final class AutoDigGui extends Screen {
|
public final class AutoDigGui extends Screen {
|
||||||
|
|
||||||
private final AutoDigModClient mod;
|
private final AutoDigModClient mod;
|
||||||
private final DigConfig config;
|
private final DigConfig config;
|
||||||
private final DigController controller;
|
private final DigController controller;
|
||||||
/** 关闭界面时是否把改动写回 mod 配置("仅关闭"会置 false) */
|
|
||||||
private boolean applyOnClose = true;
|
private boolean applyOnClose = true;
|
||||||
|
|
||||||
// 动态数值按钮(点击 +/- 后刷新显示)
|
// UI 组件引用
|
||||||
|
private TextFieldWidget overworldOresField;
|
||||||
|
private TextFieldWidget netherOresField;
|
||||||
|
|
||||||
|
// 数值显示按钮
|
||||||
private ButtonWidget levelBtn;
|
private ButtonWidget levelBtn;
|
||||||
private ButtonWidget tunnelBtn;
|
private ButtonWidget tunnelBtn;
|
||||||
private ButtonWidget countBtn;
|
private ButtonWidget countBtn;
|
||||||
private ButtonWidget armorDurBtn;
|
private ButtonWidget armorDurBtn;
|
||||||
private ButtonWidget toolDurBtn;
|
private ButtonWidget toolDurBtn;
|
||||||
private ButtonWidget fleeHBtn;
|
private ButtonWidget fleeHBtn;
|
||||||
// 矿物输入框状态
|
|
||||||
private String overworldOresDraft;
|
// 任务按钮(用于刷新高亮)
|
||||||
private String netherOresDraft;
|
private ButtonWidget btnNone;
|
||||||
private int oreInputFocused = -1; // 0 = overworld, 1 = nether
|
private ButtonWidget btnOverworld;
|
||||||
|
private ButtonWidget btnNether;
|
||||||
|
|
||||||
public AutoDigGui(AutoDigModClient mod) {
|
public AutoDigGui(AutoDigModClient mod) {
|
||||||
super(Text.literal("AutoDig"));
|
super(Text.literal("AutoDig"));
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
this.config = mod.config().copy();
|
this.config = mod.config().copy();
|
||||||
this.controller = mod.controller();
|
this.controller = mod.controller();
|
||||||
this.overworldOresDraft = this.config.overworldOres;
|
|
||||||
this.netherOresDraft = this.config.netherOres;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
this.clearChildren();
|
this.clearChildren();
|
||||||
int colX = 26;
|
int centerX = this.width / 2;
|
||||||
int x = colX;
|
int panelW = Math.min(380, this.width - 40);
|
||||||
int y = 34;
|
int left = centerX - panelW / 2;
|
||||||
|
int right = left + panelW;
|
||||||
|
int y = 30;
|
||||||
|
int gap = 4;
|
||||||
|
int rowH = 20;
|
||||||
|
int checkboxGap = 24;
|
||||||
|
int sectionGap = 14;
|
||||||
|
|
||||||
|
// ===== 标题 =====
|
||||||
|
// 居中已在 render 处理
|
||||||
|
|
||||||
// ===== 任务选择 =====
|
// ===== 任务选择 =====
|
||||||
this.taskButton("空闲", MineTask.NONE, x, y);
|
int btnW = (panelW - gap * 2) / 3;
|
||||||
this.taskButton("主世界", MineTask.OVERWORLD, x + 110, y);
|
this.btnNone = taskButton("空闲", MineTask.NONE, left, y, btnW);
|
||||||
this.taskButton("下界", MineTask.NETHER, x + 220, y);
|
this.btnOverworld = taskButton("主世界", MineTask.OVERWORLD, left + btnW + gap, y, btnW);
|
||||||
|
this.btnNether = taskButton("下界", MineTask.NETHER, left + (btnW + gap) * 2, y, btnW);
|
||||||
|
y += rowH + sectionGap;
|
||||||
|
|
||||||
// ===== 开关设置 =====
|
// ===== 通用开关(所有任务共用)=====
|
||||||
y += 30;
|
y = addSectionHeader(y, left, "通用设置", panelW);
|
||||||
this.check("保命优先", colX, y, () -> this.config.survivalFirst, v -> this.config.survivalFirst = v);
|
y = addCheckboxRow(y, left, panelW,
|
||||||
this.check("优先绕开岩浆", colX + 150, y, () -> this.config.avoidLava, v -> this.config.avoidLava = v);
|
"保命优先", () -> config.survivalFirst, v -> config.survivalFirst = v,
|
||||||
y += 24;
|
"优先绕开岩浆", () -> config.avoidLava, v -> config.avoidLava = v);
|
||||||
this.check("自动进食", colX, y, () -> this.config.autoEat, v -> this.config.autoEat = v);
|
y = addCheckboxRow(y, left, panelW,
|
||||||
this.check("自动切换工具", colX + 150, y, () -> this.config.autoTool, v -> this.config.autoTool = v);
|
"自动进食", () -> config.autoEat, v -> config.autoEat = v,
|
||||||
y += 24;
|
"自动切换工具", () -> config.autoTool, v -> config.autoTool = v);
|
||||||
this.check("杀戮光环", colX, y, () -> this.config.killAura, v -> this.config.killAura = v);
|
y = addCheckboxRow(y, left, panelW,
|
||||||
this.check("重生后回死亡点", colX + 150, y, () -> this.config.respawnReturn, v -> this.config.respawnReturn = v);
|
"杀戮光环", () -> config.killAura, v -> config.killAura = v,
|
||||||
y += 24;
|
"重生后回死亡点", () -> config.respawnReturn, v -> config.respawnReturn = v);
|
||||||
this.check("下界挖完回主世界", colX, y, () -> this.config.netherAutoReturn, v -> this.config.netherAutoReturn = v);
|
y = addCheckboxRow(y, left, panelW,
|
||||||
this.check("避难时绝杀不冒进", colX + 150, y, () -> this.config.killAuraSafe, v -> this.config.killAuraSafe = v);
|
"避难时绝杀不冒进", () -> config.killAuraSafe, v -> config.killAuraSafe = v,
|
||||||
y += 24;
|
"下界协同杀戮光环", () -> config.killAuraInNether, v -> config.killAuraInNether = v);
|
||||||
this.check("下界协同杀戮光环", colX, y, () -> this.config.killAuraInNether, v -> this.config.killAuraInNether = v);
|
y = addCheckboxRow(y, left, panelW,
|
||||||
this.check("护甲耐久保护", colX + 150, y, () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v);
|
"护甲耐久保护", () -> config.armorDurabilityProtection, v -> config.armorDurabilityProtection = v,
|
||||||
y += 24;
|
"工具耐久保护", () -> config.toolDurabilityProtection, v -> config.toolDurabilityProtection = v);
|
||||||
this.check("工具耐久保护", colX, y, () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
|
y += sectionGap;
|
||||||
|
|
||||||
// ===== 数值参数 =====
|
// ===== 主世界专用设置 =====
|
||||||
y += 32;
|
if (config.task == MineTask.OVERWORLD) {
|
||||||
this.levelBtn = this.numberRow("挖掘层(Y)", colX, y, () -> this.config.overworldLevel, v -> this.config.overworldLevel = v, -64, 320);
|
y = addSectionHeader(y, left, "主世界设置", panelW);
|
||||||
y += 24;
|
this.levelBtn = numberRow("挖掘层 (Y)", left, y, panelW,
|
||||||
this.tunnelBtn = this.numberRow("巷道长度", colX, y, () -> this.config.tunnelLength, v -> this.config.tunnelLength = v, 4, 512);
|
() -> config.overworldLevel, v -> config.overworldLevel = v, -64, 320);
|
||||||
y += 24;
|
y += rowH + gap;
|
||||||
this.countBtn = this.numberRow("下界目标数量", colX, y, () -> this.config.netherTargetCount, v -> this.config.netherTargetCount = v, -1, 512);
|
this.tunnelBtn = numberRow("主干巷道长度", left, y, panelW,
|
||||||
y += 24;
|
() -> config.tunnelLength, v -> config.tunnelLength = v, 4, 512);
|
||||||
this.armorDurBtn = this.numberRow("护甲耐久阈值%", colX, y, () -> this.config.armorDurabilityThreshold, v -> this.config.armorDurabilityThreshold = v, 1, 100);
|
y += rowH + gap;
|
||||||
y += 24;
|
|
||||||
this.toolDurBtn = this.numberRow("工具耐久阈值%", colX, y, () -> this.config.toolDurabilityThreshold, v -> this.config.toolDurabilityThreshold = v, 1, 100);
|
|
||||||
y += 24;
|
|
||||||
this.fleeHBtn = this.numberRow("下界返程血量%", colX, y, () -> this.config.netherFleeHealthThreshold, v -> this.config.netherFleeHealthThreshold = v, 0, 100);
|
|
||||||
|
|
||||||
// ===== 矿物选择 =====
|
// 矿物输入框
|
||||||
y += 32;
|
y = addTextFieldRow(y, left, panelW, "目标矿物 (逗号分隔)",
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("主世界矿物: " + this.overworldOresDraft), b -> {
|
config.overworldOres, tf -> this.overworldOresField = tf,
|
||||||
// 循环切换预设矿物组合
|
v -> config.overworldOres = v);
|
||||||
this.overworldOresDraft = nextOverworldOres(this.overworldOresDraft);
|
y += sectionGap;
|
||||||
this.refreshOreButtons();
|
|
||||||
}).dimensions(colX, y, 300, 20).build());
|
|
||||||
y += 24;
|
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("下界矿物: " + this.netherOresDraft), b -> {
|
|
||||||
// 循环切换预设矿物组合
|
|
||||||
this.netherOresDraft = nextNetherOres(this.netherOresDraft);
|
|
||||||
this.refreshOreButtons();
|
|
||||||
}).dimensions(colX, y, 300, 20).build());
|
|
||||||
|
|
||||||
// ===== 底部操作按钮 =====
|
|
||||||
y += 32;
|
|
||||||
ButtonWidget start = this.textButton("开始 (保存并运行)", b -> this.start(), x, y, 140);
|
|
||||||
ButtonWidget stop = this.textButton("停止", b -> this.stop(), x + 146, y, 90);
|
|
||||||
ButtonWidget saveClose = this.textButton("仅关闭", b -> this.closeOnly(), x + 242, y, 80);
|
|
||||||
this.addDrawableChild(start);
|
|
||||||
this.addDrawableChild(stop);
|
|
||||||
this.addDrawableChild(saveClose);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 主世界矿物预设循环 */
|
// ===== 下界专用设置 =====
|
||||||
private static String[] OVERWORLD_ORE_PRESETS = {
|
if (config.task == MineTask.NETHER) {
|
||||||
"diamond_ore,deepslate_diamond_ore",
|
y = addSectionHeader(y, left, "下界设置", panelW);
|
||||||
"diamond_ore,deepslate_diamond_ore,iron_ore,deepslate_iron_ore,coal_ore,deepslate_coal_ore",
|
this.countBtn = numberRow("目标数量 (-1无限)", left, y, panelW,
|
||||||
"diamond_ore,deepslate_diamond_ore,emerald_ore,deepslate_emerald_ore",
|
() -> config.netherTargetCount, v -> config.netherTargetCount = v, -1, 512);
|
||||||
"gold_ore,deepslate_gold_ore",
|
y += rowH + gap;
|
||||||
"lapis_ore,deepslate_lapis_ore",
|
y = addCheckboxRow(y, left, panelW,
|
||||||
"redstone_ore,deepslate_redstone_ore",
|
"挖完/背包满/血量低/耐久低 自动回主世界", () -> config.netherAutoReturn, v -> config.netherAutoReturn = v,
|
||||||
"copper_ore,deepslate_copper_ore",
|
"", () -> false, v -> {}); // 单列占满
|
||||||
"iron_ore,deepslate_iron_ore",
|
y += rowH + gap;
|
||||||
"coal_ore,deepslate_coal_ore"
|
this.fleeHBtn = numberRow("返程血量阈值%", left, y, panelW,
|
||||||
};
|
() -> config.netherFleeHealthThreshold, v -> config.netherFleeHealthThreshold = v, 0, 100);
|
||||||
|
y += rowH + gap;
|
||||||
|
|
||||||
/** 下界矿物预设循环 */
|
// 矿物输入框
|
||||||
private static String[] NETHER_ORE_PRESETS = {
|
y = addTextFieldRow(y, left, panelW, "目标矿物 (逗号分隔)",
|
||||||
"ancient_debris",
|
config.netherOres, tf -> this.netherOresField = tf,
|
||||||
"ancient_debris,nether_gold_ore",
|
v -> config.netherOres = v);
|
||||||
"nether_gold_ore",
|
y += sectionGap;
|
||||||
"nether_quartz_ore",
|
|
||||||
"ancient_debris,nether_gold_ore,nether_quartz_ore"
|
|
||||||
};
|
|
||||||
|
|
||||||
private String nextOverworldOres(String current) {
|
|
||||||
for (int i = 0; i < OVERWORLD_ORE_PRESETS.length; i++) {
|
|
||||||
if (OVERWORLD_ORE_PRESETS[i].equals(current)) {
|
|
||||||
return OVERWORLD_ORE_PRESETS[(i + 1) % OVERWORLD_ORE_PRESETS.length];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return OVERWORLD_ORE_PRESETS[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String nextNetherOres(String current) {
|
// ===== 装备耐久阈值(通用,但仅在开启保护时有效)=====
|
||||||
for (int i = 0; i < NETHER_ORE_PRESETS.length; i++) {
|
if (config.armorDurabilityProtection || config.toolDurabilityProtection) {
|
||||||
if (NETHER_ORE_PRESETS[i].equals(current)) {
|
y = addSectionHeader(y, left, "装备耐久阈值", panelW);
|
||||||
return NETHER_ORE_PRESETS[(i + 1) % NETHER_ORE_PRESETS.length];
|
this.armorDurBtn = numberRow("护甲耐久 %", left, y, panelW,
|
||||||
}
|
() -> config.armorDurabilityThreshold, v -> config.armorDurabilityThreshold = v, 1, 100);
|
||||||
}
|
y += rowH + gap;
|
||||||
return NETHER_ORE_PRESETS[0];
|
this.toolDurBtn = numberRow("工具耐久 %", left, y, panelW,
|
||||||
|
() -> config.toolDurabilityThreshold, v -> config.toolDurabilityThreshold = v, 1, 100);
|
||||||
|
y += sectionGap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshOreButtons() {
|
// ===== 底部按钮 =====
|
||||||
// 按钮在 init() 中创建,文本更新需要重新 init
|
int btnY = Math.max(y, this.height - 48);
|
||||||
this.init();
|
int bW = (panelW - gap * 2) / 3;
|
||||||
|
this.addDrawableChild(textButton("开始", b -> start(), left, btnY, bW));
|
||||||
|
this.addDrawableChild(textButton("停止", b -> stop(), left + bW + gap, btnY, bW));
|
||||||
|
this.addDrawableChild(textButton("仅关闭", b -> closeOnly(), left + (bW + gap) * 2, btnY, bW));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ButtonWidget taskButton(String label, MineTask task, int x, int y) {
|
// ========== 布局辅助 ==========
|
||||||
// 选中的任务用 [ ] 包裹,形成高亮提示
|
|
||||||
boolean selected = this.config.task == task;
|
private int addSectionHeader(int y, int left, String title, int panelW) {
|
||||||
String text = selected ? "[" + label + "]" : label;
|
// 画分隔线
|
||||||
ButtonWidget b = this.textButton(text, button -> {
|
this.addDrawableChild(ButtonWidget.builder(Text.literal(""), b -> {}).dimensions(left, y + 10, panelW, 1).build());
|
||||||
this.config.task = task;
|
// 标题文本会在 render 中绘制,这里只预留空间
|
||||||
this.init(); // 刷新选中高亮
|
return y + 18;
|
||||||
}, x, y, 104);
|
|
||||||
this.addDrawableChild(b);
|
|
||||||
return b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void check(String label, int x, int y, java.util.function.BooleanSupplier getter, Consumer<Boolean> setter) {
|
private int addCheckboxRow(int y, int left, int panelW,
|
||||||
CheckboxWidget box = CheckboxWidget.builder(Text.literal(label), this.textRenderer)
|
String label1, java.util.function.BooleanSupplier g1, Consumer<Boolean> s1,
|
||||||
.pos(x, y).checked(getter.getAsBoolean())
|
String label2, java.util.function.BooleanSupplier g2, Consumer<Boolean> s2) {
|
||||||
.callback((b, value) -> setter.accept(value)).build();
|
int halfW = (panelW - 8) / 2;
|
||||||
this.addDrawableChild(box);
|
CheckboxWidget c1 = CheckboxWidget.builder(Text.literal(label1), this.textRenderer)
|
||||||
|
.pos(left, y).checked(g1.getAsBoolean())
|
||||||
|
.callback((b, v) -> s1.accept(v)).build();
|
||||||
|
c1.setWidth(halfW);
|
||||||
|
this.addDrawableChild(c1);
|
||||||
|
if (!label2.isEmpty()) {
|
||||||
|
CheckboxWidget c2 = CheckboxWidget.builder(Text.literal(label2), this.textRenderer)
|
||||||
|
.pos(left + halfW + 8, y).checked(g2.getAsBoolean())
|
||||||
|
.callback((b, v) -> s2.accept(v)).build();
|
||||||
|
c2.setWidth(halfW);
|
||||||
|
this.addDrawableChild(c2);
|
||||||
|
}
|
||||||
|
return y + 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ButtonWidget numberRow(String label, int x, int y, java.util.function.IntSupplier getter, Consumer<Integer> setter, int min, int max) {
|
private int addTextFieldRow(int y, int left, int panelW, String label,
|
||||||
|
String initialValue, Consumer<TextFieldWidget> refSetter, Consumer<String> onChange) {
|
||||||
|
// 标签
|
||||||
|
this.addDrawableChild(ButtonWidget.builder(Text.literal(label), b -> {}).dimensions(left, y, panelW, 18).build());
|
||||||
|
y += 20;
|
||||||
|
// 输入框
|
||||||
|
TextFieldWidget tf = new TextFieldWidget(this.textRenderer, left, y, panelW, 20, Text.literal(""));
|
||||||
|
tf.setText(initialValue);
|
||||||
|
tf.setChangedListener(onChange);
|
||||||
|
tf.setMaxLength(200);
|
||||||
|
refSetter.accept(tf);
|
||||||
|
this.addDrawableChild(tf);
|
||||||
|
return y + 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ButtonWidget numberRow(String label, int left, int y, int panelW,
|
||||||
|
java.util.function.IntSupplier getter, Consumer<Integer> setter, int min, int max) {
|
||||||
|
int btnSize = 22;
|
||||||
|
int labelW = 110;
|
||||||
|
int valW = 60;
|
||||||
|
int x = left;
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("-"), b -> {
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("-"), b -> {
|
||||||
setter.accept(this.clamp(getter.getAsInt() - 1, min, max));
|
setter.accept(clamp(getter.getAsInt() - 1, min, max));
|
||||||
this.refreshNumberButtons();
|
refreshNumberButtons();
|
||||||
}).dimensions(x, y, 20, 20).build());
|
}).dimensions(x, y, btnSize, btnSize).build());
|
||||||
int valueX = x + 24;
|
x += btnSize + 4;
|
||||||
int valueW = 72;
|
// 标签按钮(不可点击)
|
||||||
ButtonWidget value = ButtonWidget.builder(Text.literal(label + " : " + getter.getAsInt()), b -> {
|
ButtonWidget lbl = ButtonWidget.builder(Text.literal(label), b -> {}).dimensions(x, y, labelW, btnSize).build();
|
||||||
}).dimensions(valueX, y, valueW, 20).build();
|
lbl.active = false;
|
||||||
value.active = false; // 纯展示
|
this.addDrawableChild(lbl);
|
||||||
this.addDrawableChild(value);
|
x += labelW + 4;
|
||||||
|
// 数值显示
|
||||||
|
ButtonWidget val = ButtonWidget.builder(Text.literal("" + getter.getAsInt()), b -> {}).dimensions(x, y, valW, btnSize).build();
|
||||||
|
val.active = false;
|
||||||
|
this.addDrawableChild(val);
|
||||||
|
x += valW + 4;
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("+"), b -> {
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("+"), b -> {
|
||||||
setter.accept(this.clamp(getter.getAsInt() + 1, min, max));
|
setter.accept(clamp(getter.getAsInt() + 1, min, max));
|
||||||
this.refreshNumberButtons();
|
refreshNumberButtons();
|
||||||
}).dimensions(valueX + valueW + 4, y, 20, 20).build());
|
}).dimensions(x, y, btnSize, btnSize).build());
|
||||||
return value;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int clamp(int v, int min, int max) {
|
private int clamp(int v, int min, int max) {
|
||||||
@@ -204,23 +221,29 @@ public final class AutoDigGui extends Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void refreshNumberButtons() {
|
private void refreshNumberButtons() {
|
||||||
this.levelBtn.setMessage(Text.literal("挖掘层(Y) : " + this.config.overworldLevel));
|
if (levelBtn != null) levelBtn.setMessage(Text.literal("" + config.overworldLevel));
|
||||||
this.tunnelBtn.setMessage(Text.literal("巷道长度 : " + this.config.tunnelLength));
|
if (tunnelBtn != null) tunnelBtn.setMessage(Text.literal("" + config.tunnelLength));
|
||||||
this.countBtn.setMessage(Text.literal("下界目标数量 : " + this.config.netherTargetCount));
|
if (countBtn != null) countBtn.setMessage(Text.literal("" + config.netherTargetCount));
|
||||||
this.armorDurBtn.setMessage(Text.literal("护甲耐久阈值% : " + this.config.armorDurabilityThreshold));
|
if (armorDurBtn != null) armorDurBtn.setMessage(Text.literal("" + config.armorDurabilityThreshold));
|
||||||
this.toolDurBtn.setMessage(Text.literal("工具耐久阈值% : " + this.config.toolDurabilityThreshold));
|
if (toolDurBtn != null) toolDurBtn.setMessage(Text.literal("" + config.toolDurabilityThreshold));
|
||||||
this.fleeHBtn.setMessage(Text.literal("下界返程血量% : " + this.config.netherFleeHealthThreshold));
|
if (fleeHBtn != null) fleeHBtn.setMessage(Text.literal("" + config.netherFleeHealthThreshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ButtonWidget taskButton(String label, MineTask task, int x, int y, int w) {
|
||||||
|
boolean selected = config.task == task;
|
||||||
|
String text = selected ? "■ " + label : "□ " + label;
|
||||||
|
ButtonWidget b = textButton(text, button -> {
|
||||||
|
config.task = task;
|
||||||
|
init(); // 完全重建 UI 以显示/隐藏对应区域
|
||||||
|
}, x, y, w);
|
||||||
|
this.addDrawableChild(b);
|
||||||
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ButtonWidget textButton(String label, ButtonWidget.PressAction action, int x, int y, int w) {
|
private ButtonWidget textButton(String label, ButtonWidget.PressAction action, int x, int y, int w) {
|
||||||
return ButtonWidget.builder(Text.literal(label), action).dimensions(x, y, w, 20).build();
|
return ButtonWidget.builder(Text.literal(label), action).dimensions(x, y, w, 22).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GUI 打开时不允许游戏暂停:否则客户端 tick 不执行,{@link DigController}
|
|
||||||
* 每帧驱动逻辑停摆,玩家会停在原地不挖矿。这里返回 false,让挖矿在 GUI
|
|
||||||
* 打开状态下也能持续进行。
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldPause() {
|
public boolean shouldPause() {
|
||||||
return false;
|
return false;
|
||||||
@@ -228,14 +251,30 @@ public final class AutoDigGui extends Screen {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
|
// 半透明背景
|
||||||
|
this.renderBackground(context, mouseX, mouseY, delta);
|
||||||
super.render(context, mouseX, mouseY, delta);
|
super.render(context, mouseX, mouseY, delta);
|
||||||
context.drawCenteredTextWithShadow(this.textRenderer, "AutoDig \u81ea\u52a8\u6316\u77ff", this.width / 2, 10, 0xFFFFFF);
|
|
||||||
|
// 标题
|
||||||
|
context.drawCenteredTextWithShadow(this.textRenderer, "AutoDig 自动挖矿", this.width / 2, 10, 0xFFFFFF);
|
||||||
|
|
||||||
|
// 当前任务/状态(底部)
|
||||||
context.drawTextWithShadow(this.textRenderer,
|
context.drawTextWithShadow(this.textRenderer,
|
||||||
"\u5f53\u524d\u4efb\u52a1: " + this.config.task.label() + " \u72b6\u6001: " + this.controller.statusText(),
|
"任务: " + config.task.label() + " 状态: " + controller.statusText(),
|
||||||
26, this.height - 24, 0xAAAAAA);
|
10, this.height - 20, 0xAAAAAA);
|
||||||
|
|
||||||
|
// 绘制分组标题(因为用 ButtonWidget 做分隔线不好看,改用直接绘制)
|
||||||
|
int centerX = this.width / 2;
|
||||||
|
int panelW = Math.min(380, this.width - 40);
|
||||||
|
int left = centerX - panelW / 2;
|
||||||
|
int y = 30 + 20 + 14; // 任务行 + sectionGap
|
||||||
|
// 通用设置标题
|
||||||
|
context.drawTextWithShadow(this.textRenderer, "通用设置", left, y - 16, 0xFFD700);
|
||||||
|
// 动态计算后续分组标题位置比较麻烦,改在 init 时记录或直接在 render 时估算
|
||||||
|
// 简化:只画标题栏背景
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 把界面临时配置写回 mod 全局配置并保存(供 controller 实时读取)。 */
|
/** 把界面临时配置写回 mod 全局配置并保存。 */
|
||||||
private void applyToMod() {
|
private void applyToMod() {
|
||||||
DigConfig target = this.mod.config();
|
DigConfig target = this.mod.config();
|
||||||
target.task = this.config.task;
|
target.task = this.config.task;
|
||||||
@@ -250,8 +289,8 @@ public final class AutoDigGui extends Screen {
|
|||||||
target.netherAutoReturn = this.config.netherAutoReturn;
|
target.netherAutoReturn = this.config.netherAutoReturn;
|
||||||
target.overworldLevel = this.config.overworldLevel;
|
target.overworldLevel = this.config.overworldLevel;
|
||||||
target.tunnelLength = this.config.tunnelLength;
|
target.tunnelLength = this.config.tunnelLength;
|
||||||
target.overworldOres = this.overworldOresDraft;
|
target.overworldOres = this.config.overworldOres;
|
||||||
target.netherOres = this.netherOresDraft;
|
target.netherOres = this.config.netherOres;
|
||||||
target.netherTargetCount = this.config.netherTargetCount;
|
target.netherTargetCount = this.config.netherTargetCount;
|
||||||
target.netherFleeHealthThreshold = this.config.netherFleeHealthThreshold;
|
target.netherFleeHealthThreshold = this.config.netherFleeHealthThreshold;
|
||||||
target.armorDurabilityProtection = this.config.armorDurabilityProtection;
|
target.armorDurabilityProtection = this.config.armorDurabilityProtection;
|
||||||
@@ -266,13 +305,15 @@ public final class AutoDigGui extends Screen {
|
|||||||
this.mod.sendToast("AutoDig: 请先选择一个挖矿任务(主世界 / 下界)");
|
this.mod.sendToast("AutoDig: 请先选择一个挖矿任务(主世界 / 下界)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 同步输入框最新值
|
||||||
|
if (this.overworldOresField != null) this.config.overworldOres = this.overworldOresField.getText();
|
||||||
|
if (this.netherOresField != null) this.config.netherOres = this.netherOresField.getText();
|
||||||
this.applyToMod();
|
this.applyToMod();
|
||||||
this.mod.sendToast("AutoDig: 已开始 " + this.config.task.label() + " 任务");
|
this.mod.sendToast("AutoDig: 已开始 " + this.config.task.label() + " 任务");
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() {
|
private void stop() {
|
||||||
// 先把本地副本的任务置空再写回,避免 close() 二次应用时把旧任务写回去
|
|
||||||
this.config.task = MineTask.NONE;
|
this.config.task = MineTask.NONE;
|
||||||
this.applyToMod();
|
this.applyToMod();
|
||||||
this.controller.bridge().cancelAll();
|
this.controller.bridge().cancelAll();
|
||||||
@@ -280,7 +321,6 @@ public final class AutoDigGui extends Screen {
|
|||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 仅关闭:丢弃未应用的改动。 */
|
|
||||||
private void closeOnly() {
|
private void closeOnly() {
|
||||||
this.applyOnClose = false;
|
this.applyOnClose = false;
|
||||||
this.close();
|
this.close();
|
||||||
@@ -289,6 +329,9 @@ public final class AutoDigGui extends Screen {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (this.applyOnClose) {
|
if (this.applyOnClose) {
|
||||||
|
// 同步输入框最新值
|
||||||
|
if (this.overworldOresField != null) this.config.overworldOres = this.overworldOresField.getText();
|
||||||
|
if (this.netherOresField != null) this.config.netherOres = this.netherOresField.getText();
|
||||||
this.applyToMod();
|
this.applyToMod();
|
||||||
}
|
}
|
||||||
super.close();
|
super.close();
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户