滚轮搬运(WheelTweak)
基本信息
| 属性 | 值 |
|---|---|
| 配置字段 | MTConfig.WheelTweak |
| 类型 | boolean |
| 默认值 | true(MTConfig.java 字段初始值) |
| 实现类 | yalter.mousetweaks.handlers.WheelHandler(静态方法 handleWheelTweak) |
| 触发输入 | 鼠标滚轮(Mouse.getDWheel(),非 LWJGL3 下每格 = 120) |
| 与 NEI 互斥 | 是,见下方「NEI 交互」 |
功能
在任意受支持的容器界面里,把鼠标悬停在某堆物品上滚动滚轮,即可批量在 光标栏位与「另一个背包」之间搬运同类型物品。
前置守卫(逐层返回)
| 顺序 | 条件 | 不满足时 |
|---|---|---|
| 1 | wheel != 0 且 selectedSlot != null |
直接返回 |
| 2 | slotCount > Constants.INVENTORY_SIZE(36) |
直接返回——栏位数不超过玩家背包大小时不存在「另一个背包」 |
| 3 | originalStack != null |
直接返回(空栏位不触发) |
| 4 | 鼠标上的物品与原栏位物品的关系正确 | 直接返回(详见下) |
第 4 条的判定(WheelHandler.java:31-35)随栏位类型分叉:
| 栏位类型 | 允许继续的条件 |
|---|---|
| 合成产物栏位 | areStacksCompatible(originalStack, stackOnMouse) 为 false 才对(鼠标上的东西与产物不同类才处理) |
| 普通栏位 | areStacksCompatible(originalStack, stackOnMouse) 为 true 才对(鼠标上的东西与原栏位同类才处理) |
只在「另一半」区间里搜索
int countUntil = slotCount - 36; // 玩家背包起始下标
if (getSlotNumber(selectedSlot) < countUntil) {
slotCounter = countUntil; // 光标在上半区 → 扫玩家背包
countUntil = slotCount;
} // 否则 → 扫 [0, slotCount-36)
搜索区间永远是光标所在区域的对侧:
- 光标在机器/箱子区(下标 <
slotCount - 36)→ 只在玩家背包[slotCount-36, slotCount)里找; - 光标在玩家背包区 → 只在机器/箱子区
[0, slotCount-36)里找。
因此滚轮不会在同一区域内来回搬运,也不会把物品丢进光标所在的栏位。
目标栏位选取
| 方向 | 遇到空栏位 | 遇到同类型且未满的栏位 | 遇到同类型且已满的栏位 | 遇到不同类型 |
|---|---|---|---|---|
| 推入(push) | 记为候选(需 sl.isItemValid(originalStack) 且非产物栏位),但继续找更好的 |
立即选中并中断扫描 | 继续扫描 | 继续扫描 |
| 拉出(pull) | 永不选中 | 立即选中并中断扫描 | 继续扫描 | 继续扫描 |
⚠️ 源码不对称点:正序扫描分支判定空栏位时会额外检查
!context.isCraftingOutputSlot(currentScreen, sl), 倒序扫描分支没有这个检查(WheelHandler.java:75)。选 搜索顺序 = 1(默认,倒序) 时,理论上可能把一个产物栏位当成空目标栏位。
若整个区间扫描完 applicableSlot == null,则直接跳出循环不做任何操作。
点击序列
numItemsToMove = 本次滚轮刻度换算出的物品数(见 滚轮物品数量缩放)。
推入时先 numItemsToMove = Math.min(numItemsToMove, count(光标栏位));
拉出时按来源栏位的实际数量和目标剩余容量逐步递减。
推入(从光标栏位 → 目标栏位)
| 情形 | 点击序列(L=左键 0,R=右键 1,均不按 Shift) |
剩余量处理 |
|---|---|---|
| 目标剩余容量 ≤ 待移数量 | L(来源) → L(目标) → L(来源) |
n -= 目标剩余容量,进入下一轮 |
| 目标剩余容量 > 待移数量,来源数量 ≤ n | L(来源) → L(目标) → L(来源) |
n = 0,结束 |
| 目标剩余容量 > 待移数量,来源数量 > n | L(来源) → R(目标) × n → L(来源) |
n = 0,结束 |
「三连击」L(来源) → L(目标) → L(来源) 的效果是:拿起来源整堆 → 放掉目标原有那堆(腾位并拾起)
→ 把来源整堆补进目标,实现整堆合并。「R × n」则是逐个右键放置,用于只取一部分。
拉出(从目标栏位 → 光标栏位)
| 情形 | 点击序列 | 剩余量处理 |
|---|---|---|
maxStack(目标) - count(目标) <= 0 |
无(break 跳出) |
结束 |
| 光标栏位剩余容量 ≤ n | L(来源) → L(光标) → L(来源) |
n -= 剩余容量,进入下一轮 |
| 剩余容量 > n,来源数量 ≤ n | L(来源) → L(光标) → L(来源) |
n -= 来源数量,继续找下一个 |
| 剩余容量 > n,来源数量 > n | L(来源) → R(光标) × n → L(来源) |
n = 0,结束 |
外层是 do { ... } while (numItemsToMove != 0);——一次滚轮输入可以跨多个栏位连锁搬运,
直到数量耗尽或找不到可用栏位。
合成产物栏位的特殊处理
光标停在产物栏位时只支持推入(拉入会复制物品,故不实现):
- 连点
numItemsToMove次L(光标栏位)——每点一次取出一个产物; - 若找到了目标栏位且操作开始前鼠标是空的(
mouseWasEmpty = stackOnMouse == null), 再点一次L(目标栏位)把取出的产物放进去; break结束,不再进入多轮循环。
因此在产物栏上滚轮,取出的产物只有第 1 个会被送走,其余停留在鼠标上。
NEI 交互
Runtime.isMouseWheelTransferActive():
if (Loader.isModLoaded("NotEnoughItems")) {
return MTConfig.WheelTweak && !NEIClientConfig.isMouseScrollTransferEnabled();
}
return MTConfig.WheelTweak;
即:若 NEI 已安装且 NEI 自己的「鼠标滚轮转移」也开着,Mouse Tweaks 整个滚轮功能
就让位给 NEI(WheelTweak 配成 true 也不生效),避免两者同时处理同一滚轮事件。
开关层级
| 层级 | 判定 | 位置 |
|---|---|---|
| 全局 | MTConfig.WheelTweak |
Runtime#isMouseWheelTransferActive |
| NEI 互斥 | !NEIClientConfig.isMouseScrollTransferEnabled() |
同上(仅 NEI 存在时) |
| 单个容器 | context.isWheelDisabledForCurrentContainer() |
Runtime#onUpdateInGui |
| 单个 mod 容器 | IMTModGuiContainer#isWheelTweakDisabled() |
ModCompatibility |
滑轮增量在打开新界面时会被主动清空:
ContainerContext.refresh中若滚轮功能处于活动状态 且未被该容器禁用,会调用一次Mouse.getDWheel()把累积的增量丢掉,避免切界面瞬间误滚。
相关条目
- 滚轮方向语义 -
WheelScrollDirection0~3 决定推入 / 拉出 - 滚轮搜索顺序 -
WheelSearchOrder0/1 决定扫描方向 - 滚轮物品数量缩放 -
ScrollItemScaling与 lwjgl3ify 检测 - 容器兼容性 - 哪些界面禁用 / 允许滚轮
- IMTModGuiContainer 接口 -
isWheelTweakDisabled()的接入点