
创建与精致存储兼容
一个用于我的世界1.20.1的兼容性模组,使机械动力模组与精妙存储模组兼容。
Create: Sophisticated Storage Compat
Sophisticated Storage 的容器现在可以作为 Create 装置上的真实存储使用 —— 在移动过程中保留 Sophisticated Storage 自家的界面、正常工作的面显示以及堆叠升级容量。
Fabric 1.20.1 · Create 6.0.8(Fabric 移植版) · Sophisticated Storage 1.3.5(Fabric 移植版)
功能说明
Create 6.0 不再自动检测装置上的物品栏;一个方块只有在 MountedItemStorageType.REGISTRY 中注册后才会被视为装置存储。Sophisticated Storage 自带了 Jade、JEI、REI、EMI、Quark、Chipped、Litematica 和 Sodium 的兼容 —— 但唯独没有 Create 的,因此它的桶和箱子在装置上只能作为无生命的装饰品。
- Sophisticated Storage 自有界面在装置上可用 —— 升级标签页、设置界面、排序和传输按钮、搜索框、高容器滚动面板、槽位记忆和不可访问槽位标记。受限桶(Limited Barrels)可以获得带有填充条的 1–4 槽位界面。
- 正常工作的面显示。已装载桶的面物品、计数和填充条会随着装置运行而跟踪其内容,而不是冻结在组装时的内容。
- 超过 64 的堆叠。经过堆叠升级的桶每个槽位可以合法容纳数千个物品。Create 的通用装载菜单会将每次交互限制在 64,并拒绝任何不是 9 到 54 之间 9 的倍数的槽位数量,这几乎排除了本模组产生的所有物品栏。
- 内容在保存后不会丢失。序列化通过 Sophisticated Storage 自身的 NBT 而非原版物品 NBT 进行,后者中 Count 只是一个字节,超大的堆叠会在装置保存的瞬间被损坏。
通过类继承结构检查,本模组涵盖了全部 48 种存储方块 —— 每个等级的木桶、受限桶、箱子和潜影盒 —— 因此未来版本或附加模组(如 sbslotupgrades)添加的方块也会被自动识别。
工作原理
有两个因素使得这个移植足够精简,完全不需要混入 Sophisticated Storage:
它的界面由 IStorageWrapper 驱动,而不是方块实体。StorageContainerMenuBase<S extends IStorageWrapper>、SettingsContainerMenu<S extends IStorageWrapper> 和 StorageScreenBase<S extends StorageContainerMenuBase<?>> 直接将包装器传入构造函数,从不查找方块。方块位置依赖完全存在于具体的子类(StorageContainerMenu 及其同类)中,而这些类被绕过了。
StorageWrapper 是与世界无关的 —— 一个纯粹的基于 NBT 的对象,其四个抽象方法都可以仅通过方块来回答。装置保留了它捕获的每个方块的方块状态,因此它的子类可以驻留在装置上。
因此整个移植就是:继承包装器、继承基础菜单、继承基础界面。ContraptionStorageScreen 只有十一行代码。
已装载的方块没有世界坐标,因此界面通过(装置实体 ID,局部 BlockPos)来寻址,并在打开界面的数据包中携带一份包装器 NBT 的副本。物品内容仍然通过菜单自身的槽位同步来自服务器。
编辑此模组前需要了解的两件事
面显示并非从物品栏读取。 渲染器读取 getRenderInfo().getItemDisplayRenderInfo(),这是一个由 ItemDisplaySettingsCategory 刷新的缓存快照。Sophisticated Storage 为此注册的监听器 itemChanged(int) 以 getCurrentServer().isOnThread() 检查开头,因此在客户端上不会执行任何操作——这就是为什么将堆叠写入客户端方块实体自身不会产生可见效果。itemsChanged() 没有这样的保护,因此 afterSync 会显式调用它。
已同步的装载存储绝不能在客户端上报告脏状态。 AbstractContraptionEntity.tick 会在双端无保护地调用 MountedStorageManager.tick,而后者会对脏存储调用 sendToClientsTracking,这在客户端会抛出 "Only supported on server worlds!" 异常。Create 自身的存储只会因服务器端修改而变脏;本模组将标志挂接在 Sophisticated Storage 的通用保存回调上,该回调在反序列化簿记期间也会触发,因此 SophisticatedMountedStorage 在构造后清除标志,并在 afterSync 中锁定 clientSide 标志。如果此问题回归的症状是:放置一个被扳手拆卸的装置物品时崩溃。
为什么不使用非世界方块实体
为每个已装载方块物化一个真正的 StorageBlockEntity 可以满足 Sophisticated Storage 的所有假设,但它的构造函数会在全局的 ServerChunkEvents.CHUNK_UNLOAD 和 ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD 事件上注册监听器,而 Fabric 事件无法注销。每个已装载木桶产生的一次性方块实体会泄漏一个监听器直到游戏结束。
构建
先将编译期 jar 放入 libs/ —— 参见 libs/README.md。
./gradlew build
输出位于 build/libs/。需要 Java 17+ 进行编译(针对 1.20.1 工具链运行 Gradle 需要 Java 21)以及 Gradle 8.x —— Loom 1.7.4 不接受 Gradle 9,因此请使用附带的包装器。
项目使用 yarn mappings 编译。要确认构建确实完成了重映射,可以反汇编输出:Minecraft 引用应为 intermediary(class_2338、class_1657),而 Create 和 Sophisticated Storage 的引用保留其真实名称。
测试更改
在对本模组进行任何更改后,必须拆卸并重新组装装置。装载只发生在组装时 —— MountedStorageManager.read() 反序列化已保存的内容,并且不会再查询注册表。
由较旧版本组装的装置会反序列化为空存储,而不会抛出异常,因为如果任何一个存储抛出异常,Create 会放弃整个装置的读取过程。
已知限制
- 升级在装载期间不会 tick。 升级物品会被保留并显示在界面中,任何通过物品栏处理器运作的功能(槽位限制、过滤器、输入输出过滤器)仍然生效 —— 但磁铁、泵、喂养、熔炼、合成等其余功能会被暂停。
- 已装载箱子的显示侧选择被隐藏。 该标签页通过读取世界方块状态来决定是否提供控制选项,而已装载方块不具备这样的位置。
- 控制器网络方块被排除在外(控制器、存储输入/输出、存储链接)—— 它们通过网络解析其内容,而移动中的装置不属于任何网络。
许可证
MIT —— 参见 LICENSE。Create 和 Sophisticated Storage 归各自作者所有;此处不重新分发其中任何部分。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。