网络协议(Network Protocol)
基本信息
| 属性 | 值 |
|---|---|
| 通道 | craftingtweaks(NetworkRegistry.INSTANCE.newSimpleChannel(CraftingTweaks.MOD_ID)) |
| 包装类 | SimpleNetworkWrapper,实例字段 NetworkHandler.instance |
| 初始化 | CraftingTweaks.init() → NetworkHandler.init() |
| 封包总数 | 5 |
| 方向 | 全部 C→S,注册参数一律 Side.SERVER,服务端不回任何包 |
| 判别器 | 离散 ID 0/1/2/5/6 —— 3 和 4 空缺未使用 |
| 线程模型 | 全部 handler 用 gtnhlib ServerThreadUtil.addScheduledTask 包装 |
| 版本检查 | @NetworkCheckHandler checkNetwork 恒返回 true,只记录 isServerSideInstalled |
checkNetwork 在服务端侧检测对端 map 是否含 craftingtweaks 键,
写入 CraftingTweaks.isServerSideInstalled;该布尔值决定所有操作走封包还是走客户端本地模拟。
封包总表
| ID | Message 类 | Handler | 方向 | 线格式 | 触发的操作 |
|---|---|---|---|---|---|
0 |
MessageRotate |
HandlerRotate |
C→S | byte id + boolean counterClockwise |
旋转网格(Shift = 逆时针) |
1 |
MessageClear |
HandlerClear |
C→S | byte id + boolean force |
清空网格(Shift = 强制丢物) |
2 |
MessageBalance |
HandlerBalance |
C→S | byte id + boolean spread |
均分(Shift = 平铺) |
5 |
MessageTransferStack |
HandlerTransferStack |
C→S | byte id + int slotNumber |
W 键正向点击投料 |
6 |
MessageCompress |
HandlerCompress |
C→S | int slotNumber + boolean isDecompress + boolean compressAll |
压缩 / 拆分物品 |
⚠️ ID 3 与 4 没有被任何 registerMessage 占用。这不是笔误,
源码里确实跳号,跨 mod 复用本通道时需注意不要占用。
⚠️ id 字段声明为 int 但线上只写 1 字节(buf.writeByte(id) /
buf.readByte())。由于本 mod 从不创建 id != 0 的 tweakId
(SimpleTweakProviderImpl 的三个按钮恒传 0,
只有 GanysDualWorktableTweakProvider 会用 1),实际取值域是 {0, 1},
不会溢出。但注意 ClientProxy 的热键路径硬编码传 0:
new MessageRotate(0, isShiftDown)
new MessageClear(0, isShiftDown)
new MessageBalance(0, isShiftDown)
new MessageTransferStack(0, slot.slotNumber)
即热键只能操作第 0 号网格;双网格容器(如 Gany’s 双工作台的 id=1) 只能用鼠标点按钮操作。
字段语义
| 字段 | 类型 | 含义 |
|---|---|---|
id(全部 4 个 tweak 包) |
int / 线上 byte |
tweakId,即第几个合成网格 |
counterClockwise |
boolean |
false 顺时针 / true 逆时针 |
force |
boolean |
清空时背包放不下是否丢到地上 |
spread |
boolean |
true 走 spreadGrid / false 走 balanceGrid |
slotNumber(投料) |
int |
源槽的 Slot.slotNumber |
slotNumber(压缩) |
int |
目标物品所在槽的 Slot.slotNumber,服务端必须属于 InventoryPlayer |
isDecompress |
boolean |
true 拆分 / false 压缩 |
compressAll |
boolean |
是否循环到整摞耗尽 |
注意 MessageTransferStack 的两个字段是 public(其余 Message 的
id 是 private),handler 直接读 message.slotNumber。
各 handler 的服务端校验
| Handler | 校验链 |
|---|---|
HandlerRotate |
container != null → getProvider(container) != null |
HandlerClear |
同上 |
HandlerBalance |
同上,随后按 isSpread() 二选一 |
HandlerTransferStack |
container != null 且 0 <= slotNumber < inventorySlots.size() → provider 存在 → canTransferFrom 且 !(slot instanceof SlotCrafting) → slotStack != null → slot.canTakeStack(player) → transferIntoGrid 返回 true → 比对 slotStack.stackSize 与 oldStack.stackSize,无变化则 return |
HandlerCompress |
container != null → 0 <= slotNumber < size → mouseSlot.inventory instanceof InventoryPlayer → mouseStack != null → compressAnywhere || provider != null |
所有 handler 的 onMessage 都 return null(无回复包),
并且实际逻辑都包在 ServerThreadUtil.addScheduledTask 惰性里。
服务端缺失时的降级
isServerSideInstalled == false 时 ClientProxy 在聊天栏发一次提示
(missingMessageSent 去重):
This server does not have Crafting Tweaks installed. Functionality may be limited.
之后所有 tweak 走 ClientProvider 本地模拟,但如
合成网格操作 所述,requiresServerSide() 为真的容器
(phantomItems)仍会静默失效。