组合键绑定

基本信息

属性 值
所属维度 keybinding
核心接口 com.blamejared.controlling.keybinding.ComboKeyBinding
实际实现 com.blamejared.controlling.mixins.early.MixinKeyBinding(Mixin 到原版 net.minecraft.client.settings.KeyBinding)
侧别 纯客户端(Mixin 用 addClientMixins 注册)
修饰键取值 见 修饰键枚举

功能

Controlling 自身不注册任何按键(源码中没有一处 new KeyBinding)。它通过 Mixin 在原版 KeyBinding 类上挂载 ComboKeyBinding 接口,使所有按键绑定——原版的以及其他 mod 注册的—— 都自动获得一个额外的修饰键字段,从而支持组合键。

每个 KeyBinding 实例因此多出两个字段(MixinKeyBinding 中标注 @Unique):

字段 初始值 含义
controlling$keyModifier KeyModifier.NONE 当前生效的修饰键
controlling$defaultKeyModifier KeyModifier.NONE 默认修饰键("恢复默认"时回到的值)

构造函数 <init> 的 TAIL 注入点会把这两个字段再次置为 KeyModifier.NONE, 保证新实例一定是干净的初始状态。

接口方法

ComboKeyBinding 共 12 个方法,全部以 controlling$ 前缀命名(mixin unique 命名空间约定, 避免与原版或其他 mod 的成员冲突):

方法 作用
controlling$getKeyModifier() 读取当前修饰键
controlling$getDefaultKeyModifier() 读取默认修饰键
controlling$setKeyModifier(KeyModifier) 设置当前修饰键;传入 null 归一化为 NONE
controlling$setDefaultKeyModifier(KeyModifier) 设置默认修饰键;null 归一化为 NONE
controlling$setKeyModifierAndCode(KeyModifier, int) 同时设置修饰键与键码
controlling$getDisplayName() 返回 GUI 显示名,如 Ctrl + G
controlling$conflicts(KeyBinding) 是否构成硬冲突
controlling$hasKeyCodeModifierConflict(KeyBinding) 是否构成软冲突(同键码、不同修饰键)
controlling$isSetToDefaultValue() 键码与修饰键是否都等于默认值
controlling$setToDefault() 恢复默认键码 + 默认修饰键
controlling$isModifierActive() 当前所需修饰键是否按下

显示名规则

controlling$getDisplayName():

  • GameSettings.getKeyDisplayString(keyCode) 作为基础键名;
  • 若修饰键为 NONE 或 keyCode == Keyboard.KEY_NONE,直接返回基础键名;
  • 否则返回 modifier.getDisplayName() + " + " + keyName,例如 Ctrl + G、Shift + K。

硬冲突与软冲突

这是本 mod 区分冲突严重度的核心逻辑,也是列表里 [ ] 显示为金色或红色的依据。

controlling$conflicts(other) —— 硬冲突,两个条件都不满足即返回 false:

  1. other == null → 不冲突;
  2. this.keyCode != other.getKeyCode() 或 this.keyCode == Keyboard.KEY_NONE → 不冲突;
  3. 取 other 的修饰键(非 ComboKeyBinding 实例一律按 KeyModifier.NONE 计), 返回 this.keyModifier == otherModifier。

即:键码相同 且 修饰键相同才算冲突。G 与 Ctrl + G 不算冲突。

controlling$hasKeyCodeModifierConflict(other) —— 软冲突:

  1. other 不是 ComboKeyBinding → false;
  2. !controlling$conflicts(other) → false;
  3. 返回 this.keyModifier != combo.controlling$getKeyModifier()。

即:键码相同 但 修饰键不同。源码注释给的例子是 SHIFT 与 SHIFT + G 这类较轻度的冲突。

判定与重置

  • controlling$isSetToDefaultValue():keyCode == keyCodeDefault && keyModifier == defaultKeyModifier —— 键码和修饰键都回到默认才算"未改动"。这直接控制 GUI 里"恢复默认"按钮是否可点。
  • controlling$setToDefault():keyCode = keyCodeDefault; keyModifier = defaultKeyModifier

持久化格式

MixinGameSettings(Mixin 到原版 GameSettings)在 options.txt 的按键行后追加第三个字段。

写入:saveOptions 把对 PrintWriter.println(String) 的调用重定向,凡是以 key_ 开头的行, 经 controlling$appendModifierToKeyLine 处理——按 ":" 分割且只取 2 段后重组:

情况 输出
该按键是 ComboKeyBinding 且修饰键不为 NONE key_<描述>:<键码>:<MODIFIER>
修饰键为 NONE 原样输出 key_<描述>:<键码>(不写第三段)
找不到对应 KeyBinding 原样输出

<MODIFIER> 用的是 KeyModifier.name(),即序列化名 CONTROL / SHIFT / ALT。

读取:loadOptions 在调用原版 KeyBinding.setKeyCode(int) 之前拦截,若该行字段数 astring.length > 2 且该按键是 ComboKeyBinding,则用 KeyModifier.fromSerializedName(astring[2]) 设置修饰键。fromSerializedName 对任何无法识别的字符串都回落为 NONE。

因为第三段只在该按键真正有修饰键时才写入,所以原版写出的两字段格式与本 mod 完全兼容, 原版读取时也会忽略多出的第三段。匹配按键时用 "key_" + keyBinding.getKeyDescription() 精确比对。

相关条目