网络协议(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)仍会静默失效。

相关条目