配方消歧
从 NEI 导入配方时,同一个槽位常常有多个候选物品(NEI 的一个槽位往往是
若干可选 ItemStack 的数组)。GuiRecipe 用一个 disamb 映射把这些候选存起来,
并用一个浮层让玩家挑一个代表。
基本信息
| 属性 | 值 |
|---|---|
| 实现类 | me.towdium.jecalculation.gui.guis.GuiRecipe |
| 核心字段 | EnumMap<IO, Map<Integer, List<ILabel>>> disamb(IO → 槽位下标 → 候选标签) |
| 触发条件 | p.two.getLabels().size() > 1(该槽位的代价列表含多于一个标签) |
| 浮层类 | 内部类 WDisamb(IO type, int idx) |
| 数量增减 | handleLabelScroll(WLabel w, int diff) 静态方法 |
候选是怎么生成的
GuiRecipe.extract(EnumMap<IO, List<Trio<ILabel, CostList, CostList>>> src, IO type, Class<?> context):
l = src.get(type); 若为 null → 返回空列表
for (i = 0; i < l.size(); i++):
p = l.get(i)
ret.add(p.one) // ← 代表标签直接占位
if (p.two.getLabels().size() > 1): // ← 触发条件
raw = p.two.getLabels()
suggest = CONVERTER.guess(raw, context).one
suggest.addAll(raw) // ← 建议在前,原始候选在后
disamb.computeIfAbsent(type, ...).put(i, suggest)
return ret
候选列表的构成是 SUGGEST 的结果 + 原始候选。也就是说玩家总会先看到
「更宽松的建议标签」(例如把某个具体的机器零件建议成 oreDict),
若都不满意再看到 NEI 给的原始 ItemStack。这依赖
标签类型与合并 里 CONVERTER 的
SUGGEST / FALLBACK 两级优先级。
transfer(...) 每次调用开头都会 disamb.clear(),所以重新从 NEI 导入会丢弃上次的消歧记录。
浮层按钮
每个槽位旁有一排 6 个按钮(WAmount 内部类构造):
| 按钮 | 坐标 | 提示键 | 行为 |
|---|---|---|---|
# |
x+78, y-1 |
recipe.to_percent |
temp.getLabel().setPercent(true) |
% |
同上(同坐标,互斥显示) | recipe.to_amount |
temp.getLabel().setPercent(false) |
| 取物图标 | x+21, y-24 |
recipe.pick |
把 temp 挂到 JecaGui.getCurrent().hand,并把原槽位置空 |
| 确认 | x+59, y-24 |
recipe.confirm |
set(temp.getLabel(), type, idx) —— 把输入框的值写回槽位 |
| 删除 | x+78, y-24 |
recipe.delete |
set(ILabel.EMPTY, type, idx) —— 清空槽位 |
| 消歧 | x+40, y-24 |
recipe.disamb |
打开 WDisamb 浮层 |
# 与 % 两个按钮坐标完全相同(都在 x+78, y-1),靠 update() 切换可见性:
当前标签是绝对数量时显示 #(可切到百分比),是百分比时显示 %(可切回绝对数量)。
消歧按钮的禁用逻辑
Map<Integer, List<ILabel>> entry = GuiRecipe.this.disamb.get(type);
if (entry != null && entry.containsKey(idx)) {
disamb.setListener(i -> GuiRecipe.this.setOverlay(new WDisamb(type, idx)));
} else disamb.setDisabled(true);
该槽位没有多个候选时,消歧按钮是灰的。
数量输入的校验
WAmount 里的 WTextField 监听器有一套手写校验:
try:
amount = Long.parseLong(text.getText())
acceptable = amount > 0
if (!acceptable) amount = 1 // 非正数被改写为 1
catch NumberFormatException:
acceptable = text.getText().isEmpty()
amount = 1
text.setColor(acceptable ? COLOR_TEXT_WHITE : COLOR_TEXT_RED)
yes.setDisabled(!acceptable)
temp.setLabel(temp.getLabel().setAmount(amount))
| 输入 | acceptable |
颜色 | 确认按钮 |
|---|---|---|---|
| 正整数 | true |
白 | 可用 |
0 / 负数 |
false(量被改成 1) |
红 | 禁用 |
| 非数字非空 | false(量被改成 1) |
红 | 禁用 |
| 空串 | true(量被改成 1) |
白 | 可用 |
即空输入是合法的(视为 1),但「非数字且非空」是红色非法输入。 注意非法输入并不会阻止数量被改成 1,只是把确认按钮禁掉。
数量增减
handleLabelScroll 是一个静态方法,按滚轮差值反复调用 increaseAmount() /
decreaseAmount():
static boolean handleLabelScroll(WLabel w, int diff) {
ILabel l = w.getLabel();
for (int i = 0; i < Math.abs(diff); i++) l = diff > 0 ? l.increaseAmount() : l.decreaseAmount();
w.setLabel(l, true);
return true;
}
恒返回 true,即永远宣称「事件已处理」,滚轮不会继续冒泡给父控件。
增减步长由 Impl.increaseAmount() / decreaseAmount() 定义(对百分比标签会相应调整量级)。
保存前的重复检测
refresh() 在每次变化后重新校验当前配方能否保存:
try {
Recipe r = toRecipe();
boolean d = dest == null ? Controller.hasDuplicate(r) : Controller.hasDuplicate(r, dest.one, dest.two);
save.setDisabled(d);
if (dest != null) copy.setDisabled(d);
} catch (IllegalArgumentException e) {
save.setDisabled(true);
copy.setDisabled(true);
}
IllegalArgumentException 来自 Recipe 构造器——它会拒绝两种非法配方:
| 校验 | 条件 |
|---|---|
a |
input / output / catalyst 任一列表的最后一个元素是 ILabel.EMPTY |
b |
input 或 output 中有列表全部元素都是 ILabel.EMPTY |
toRecipe() 构造前会 trim(...) 掉尾部所有的 EMPTY(从后往前 remove),
所以第一个条件在正常路径下不会触发。