控制设置界面
基本信息
| 属性 | 值 |
|---|---|
| 主界面类 | com.blamejared.controlling.client.gui.GuiNewControls |
| 列表类 | com.blamejared.controlling.client.gui.GuiNewKeyBindingList |
| 继承 | GuiNewControls extends GuiControls(原版),GuiNewKeyBindingList extends GuiKeyBindingList(原版) |
| 侧别 | @SideOnly(Side.CLIENT) |
| 注入方式 | ClientEventHandler 监听 GuiOpenEvent 替换 event.gui |
| 顶部选项 | INVERT_MOUSE、SENSITIVITY、TOUCHSCREEN |
功能
本 mod 不注册任何按键,它改造的是按键设置界面本身:接管原版 GuiControls,
加入搜索、过滤、排序、单条重置、组合键编辑与可视化键盘。
接管方式
ClientEventHandler.openGui(GuiOpenEvent) 的判定是
event.gui instanceof GuiControls && !(event.gui instanceof GuiNewControls),
满足则替换为 new GuiNewControls(Minecraft.getMinecraft().currentScreen, Minecraft.getMinecraft().gameSettings)。
整个逻辑包在 try/catch 中,异常仅 printStackTrace(),不会阻断界面打开。
事件在
Controlling.init中通过MinecraftForge.EVENT_BUS.register(new ClientEventHandler())注册, 且init在服务端直接return,所以这一切都是纯客户端的。
按钮 ID 总表
底部与顶部按钮(GuiNewControls)
| 常量 | ID | 位置 | 初始文案 |
|---|---|---|---|
DONE_BUTTON_ID |
1001 | 右下 | gui.done |
RESET_ALL_KEYS_BUTTON_ID |
1002 | 左下 | controls.resetAll |
SHOW_UNBOUD_BUTTON_ID |
1003 | 右下上一行 | options.showNone |
SHOW_CONFLICTS_BUTTON_ID |
1004 | 右下上一行左 | options.showConflicts |
SEARCH_KEYNAME_BUTTON_ID |
1005 | 左下复选框 | options.key |
SEARCH_CATEGORYNAME_BUTTON_ID |
1006 | 左下复选框 | options.category |
VISUAL_KEYBOARD_BUTTON_ID |
1007 | 右下上两行 | options.keyMap |
SORT_TYPE_BUTTON_ID |
1008 | 右下上两行右 | options.sort + ": " + 下一个排序名 |
另有 3 个原版 GameSettings.Options(INVERT_MOUSE / SENSITIVITY / TOUCHSCREEN)按
returnEnumOrdinal() 作为按钮 ID,ID 小于 100 时按原版逻辑切换选项值。
每行按键的按钮(GuiNewKeyBindingList.KeyEntry)
| 按钮 | ID | 宽 | 说明 |
|---|---|---|---|
btnChangeKeyBinding |
2000 | 95 | 进入按键编辑态,文案为组合显示名或原版键名 |
btnResetKeyBinding |
2001 | 50 | controls.reset,重置该行 |
btnVisualKeyboard |
2002 | 18 | 纯图标按钮(代码绘制的键盘小图标),打开 可视化键盘 |
横坐标分别为 x + 105(改键)、x + 204(键盘图标)、x + 224(重置)。
恢复全部按键
按钮 ID 1002 采用两步确认:
- 第一次点击:
confirmingReset = true,文案变为options.confirmReset(英文Click to confirm reset!),直接 return 不执行任何修改; - 第二次点击:
confirmingReset = false,文案还原为controls.resetAll,然后 遍历mc.gameSettings.keyBindings:ComboKeyBinding→controlling$setToDefault()(同时恢复键码和修饰键);- 其他 →
keyBinding.setKeyCode(keyBinding.getKeyCodeDefault()); 之后调用this.options.saveOptions()与KeyBinding.resetKeyBindingArrayAndHash()。
按钮可用性:drawScreen 每帧遍历 options.keyBindings 计算 isModified——
组合键走 controlling$isSetToDefaultValue(),普通键比较 getKeyCode() 与 getKeyCodeDefault()。
isModified == false 时按钮禁用、文案保持 controls.resetAll,并把 confirmingReset 强制复位。
单行重置(ID 2001)的可用性判定逻辑与之完全一致,但执行时额外调用
mc.gameSettings.setOptionKeyBinding(this.keybinding, this.keybinding.getKeyCodeDefault())
再 KeyBinding.resetKeyBindingArrayAndHash()。
组合键录入流程
这是本界面最复杂的部分,源码用"待定(pending)+ 释放后才提交"两段式实现。
为什么延迟提交:按键编辑期间玩家按下的 Ctrl、G 等会立刻触发游戏内行为。 先记下待定绑定、等玩家松开后再写入,可以避免误触发。
| 阶段 | 行为 |
|---|---|
| 1. 进入编辑 | selectKeyBinding(keyBinding, showVisualKeyboard):设 buttonId,若走键盘面板则把当前修饰键带入面板 |
| 2. 玩家按修饰键 | keyTyped 中 KeyModifier.isKeyCodeModifier(keyCode) 命中且为组合键 → 记 selectedModifier / selectedModifierKeyCode,不关闭编辑态 |
| 3. 玩家按主键 | schedulePendingBinding(buttonId, inputKeyCode, getSelectedModifierForBinding()) 记下三元组,仍不关闭 |
| 4. 释放 | applyPendingBindingIfReleased()(由 updateScreen 每帧调用)检测到输入已松开 → 写入 |
getSelectedModifierForBinding() 的推断规则:若显式选过修饰键就用它,
否则退回 KeyModifier.getActiveModifier()(即"此刻正按着哪个修饰键")。
走可视化键盘时则取键盘面板的当前修饰键。
isPendingInputStillActive() 判定"还没松开"的三种情况:
pendingModifier != NONE且pendingModifier.isActive();pendingKeyCode <= -100(鼠标键编码,-100 + 按钮号)且该鼠标键仍按下;pendingKeyCode落在Keyboard.KEY_NONE与 256 之间的键盘键码区间且仍按下。
注意
typedChar + 256编码的字符键不在上述第三种判定内,会被视为已松开。keyTyped中inputKeyCode的取值是keyCode != KEY_NONE ? keyCode : (typedChar > 0 ? typedChar + 256 : KEY_NONE)。
裸修饰键单独绑定:captureModifierOnlyBinding() 处理"玩家只按了 Ctrl 就松手"的情况,
调 schedulePendingBinding(buttonId, selectedModifierKeyCode, NONE),
即把该修饰键本身绑成裸热键。
按 ESC 取消:对组合键调用 controlling$setKeyModifierAndCode(KeyModifier.NONE, Keyboard.KEY_NONE)
(清空绑定),再 options.setOptionKeyBinding(buttonId, Keyboard.KEY_NONE),并清空全部待定状态。
鼠标键绑定:在 mouseClicked 中,buttonId != null 时若为组合键则
schedulePendingBinding(buttonId, -100 + mb, getSelectedModifierForBinding()),
即鼠标键与修饰键同样可以组成组合键。
统一收尾:键盘、鼠标两条录入路径结束后都会调用 KeyBinding.resetKeyBindingArrayAndHash()
并刷新 field_152177_g(Minecraft.getSystemTime(),原版用于与其他 mod 协调按键冲突的时间戳)。
搜索框
- 宽度 148、高 18,
setCanLoseFocus(true); - 文本变化时(
updateScreen中与lastSearch比对)触发filterKeys(); - 焦点状态下右键(
mb == 1)点击会清空文本; - ESC 在焦点状态下只取消焦点;不在焦点状态且未开键盘面板时,ESC 走
mc.displayGuiScreen(null)+mc.setIngameFocus()。
1.7.10 混淆成员的绕行
1.7.10 中 GuiScreen 的若干方法仍未被混淆回可读名,源码显式绕行:
| 调用 | 实际含义 | 处理方式 |
|---|---|---|
guiNewKeyBindingList.func_148179_a(mx, my, mb) |
GuiListExtended.mouseClicked |
直接调用 |
guiNewKeyBindingList.func_148181_b(...) |
GuiListExtended.mouseReleased |
直接调用 |
superSuperMouseClicked |
原版 GuiScreen.mouseClicked |
重写实现,手工 post GuiScreenEvent.ActionPerformedEvent.Pre/Post 并调用 func_146113_a(点击音效) |
superSuperMouseReleased |
原版 GuiScreen.mouseReleased |
重写实现 selectedButton.mouseReleased |
superSuperKeyTyped |
原版 GuiScreen.keyTyped |
重写实现 ESC 关闭界面 |
superSuperMouseClicked 之所以必须重写:它需要显式向 Forge 事件总线投递
GuiScreenEvent.ActionPerformedEvent.Pre 与 .Post,否则其他 mod 的界面事件钩子会失效。
转发过程用 try/catch 包住 IOException,失败时以 Controlling.LOGGER.error 记录。
buttonList 与 selectedButton 两个原版私有字段通过 META-INF/controlling_at.cfg
访问转换器开放。
列表渲染
GuiNewKeyBindingList 的尺寸与行为:
| 属性 | 值 |
|---|---|
width / right |
controls.width + 45 |
top |
63 |
bottom |
controls.height - 80 |
getScrollBarX() |
原版 + 35 |
getListWidth() |
原版 + 32 |
构造时克隆 gameSettings.keyBindings 并 Arrays.sort,跳过分类名以 .hidden 结尾的按键,
按分类变化插入 CategoryEntry 标题行,每条按键生成一个 KeyEntry。
按键名标签右对齐到 x + 95,最大宽度 91;超宽时用 trimWithEllipsis 截断并追加 ...。
鼠标悬停在被截断的标签上时记录到 hoveredKeyDescription,
由控制界面绘制 tooltip(开启可视化键盘时则改为绘制键盘 tooltip)。
冲突标记:行首/行尾的括号颜色按严重度区分——
| 标记 | 颜色 | 条件 |
|---|---|---|
> … <(黄色 + 下划线) |
EnumChatFormatting.YELLOW |
该行正处于编辑态(controlsScreen.buttonId == keybinding) |
[ … ] 金色 |
EnumChatFormatting.GOLD |
硬冲突且 modConflict 仍为真,即同键码不同修饰键的轻度冲突 |
[ … ] 红色 |
EnumChatFormatting.RED |
存在完全相同的键码 + 修饰键绑定 |
计算时 modConflict 初值为 true,对每个冲突项取 &= controlling$hasKeyCodeModifierConflict(key);一旦出现非 ComboKeyBinding 的同键码绑定,
会直接 break 且不参与该标志的收窄,因此显示为红色。
scrollToKeyBinding(keyBinding) 可把指定绑定滚动到视口中央;
showKeyBinding 在目标当前被过滤掉时,会先清空搜索框、重置 displayMode 为 ALL、
复位两个显示按钮的文案,再重新过滤并滚动。