本帖最后由 u.s.knowledge 于 2019-2-19 05:31 编辑


本指南为 williewillus 的《1.13 Port Primer》的简体中文译文,原文地址 https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a
译者(按 ID 自然排序):3TUSK (@u.s.knowledge), ustc-zzzz (@ustc-zzzz)


一些说明:
  • 本译文将尽最大努力保持更新,但还请时刻以原文为准。
  • 译文假定读者对基于 MinecraftForge 的 Mod 开发有相当的了解。因为原文就是这样的...
  • 有鉴于本文有少量国内开发者圈子及 Mod 圈子中不常见的东西,译文已针对性地追加了链接及译注。
  • 应该会做一份 Markdown 的以供不时之需。毕竟一开始翻译的时候也在用 Markdown。
  • 部分链接无法直接访问。
  • 特别鸣谢 https://mm2bc.ustc-zzzz.net/
  • 当前翻译版本对应 commit https://gist.github.com/williewi ... 65c47b9414d516b3490



本更新指南在 CC0 协议下发布,可看作等同于发布于公有领域,怎么用都行。

但是!本指南仍在不断更新中,与最终 Forge 1.13 发布时的情况可能有各种出入。若你打算基于本作品制作别的什么,我建议你应该时刻跟踪 Forge 1.13 发布后的更新,或者留下原文的链接以供读者随时查阅。(译注:原文地址在开头。)

预先警告

  • 如果你向 ResourceLocation 传入非小写下划线风格的字符串,那么所有的 ResourceLocation 都会抛出一个异常,而不是把它们静默地全部变成小写字母,因此你或许需要检查并调整你代码里所有的字符串常量。更准确地说,ResourceLocation 的 namespace/doamin 部分必须是小写拉丁字母或数字、下划线 _、连字符 - 或点 .。路径(path)部分有一样的限制,但相比 namespace/domain 部分来说多允许使用斜杠 /
  • Mod 加载周期的事件现在是并发发布(RUN IN PARALLEL)的。如果你需要和别的 Mod 打交道,你必须做好并发控制(CONCURRENCY CONTROL)。
    • 绝大多数时候直接用 IMC 系统就好了?。
    • 不然的话,先去学多线程,或者去 Discord 问也可以。
  • Gradle 相关:https://gist.github.com/mcenderdragon/6c7af2daf6f72b0cadf0c63169a87583(译注:暂缓开坑,说不定明天就翻译出来了)
  • MCP 名变更:https://github.com/ModCoderPack/MCPBot-Issues/blob/master/migrations/1.12-to-1.13.md
    • Block.GRASS(原版的草方块)重命名为 Block.GRASS_BLOCKBlock.GRASS 现在是指那个原版的草丛(译注:空手能打碎,会掉小麦种子那个)。请检查你的代码确保没有 bug!
  • 注册表相关事件限制在 Mod 专有的事件总线上发布。查阅 EventBusSubsciber 注解的 bus 字段获得详细信息。

渲染相关的变化

  • ModelLoader.setCustomModelResourceLocation 已移除,因为扁平化之后它已经完成了它的历史使命。
    • 物品模型的路径和注册名直接挂钩。换言之,注册名为 foomod:fooitem 的物品,对应的 JSON 模型默认路径是 assets/foomod/models/item/fooitem.json
    • 如果你需要指定别的路径,请考虑通过 ModelBakeEvent 为物品指定新的 IBakedModel
  • StateMapper 已移除,取而代之的是写死的 BlockModelShapes 中的默认逻辑。
    • 这样做的理由很简单,原版扁平化之后 StateMapper 就没有存在意义了。
    • 对于 Mod 来说,你如果也用到了 StateMapper 那你也应该扁平化(加了后缀?扁平化。拆分方块?扁平化。)
  • 原版 BlockState JSON 现在更加智能了。
    • 之前:variant string 必须使用一种非常特殊的格式,实际上是基于 StateMapper 的字典序排序的方块属性键值对。
    • 现在:先按 , 分割字符串,然后动态构建为 Predicate<IBlockState>。如此一来 StateMapperignore 功能就被取代了:对于不影响模型的属性,你只需要不写就可以了。
    • 暂时不清楚重叠的 Predicate 如何处理,初步调查显示是报错。
    • 副作用:之前没有任何属性的占位属性 "normal" 现在改为 ""(译注:没错就是空字符串),这样定义的 Predicate<IBlockState> 能匹配任意方块状态。
  • 原版 JSON 模型现在稍微更智能了。
    • {} 现在是合法 JSON 模型,代表空模型。
    • 只设定 textures 不设定 elements 现在也是合法的了,在设定 TileEntityRenderer(译注:原文为 TESR,这里采用新版 MCP 使用的新名称 TileEntityRenderer)对应的方块破坏粒子效果时可以用到。
  • textures/blocks 目录改名 textures/block(译注:blocks 变为单数),textures/items 目录改名 textures/item(译注:items 变为单数),和 model 文件夹相对应。大量原版纹理文件也经历了重命名以保持风格统一。
    • 没错,这个可以自动完成。查一下 sedgrep,或者写个 shell 或 Python 脚本也成。(译注:$ man sed$ man grep are your friend. 如果你不知道这俩是什么,那你大概只能手动完成这个了。)
    • 纹理错误现在应该会明确指明错误的具体位置了。(你清理你的 Mod 让它不刷一堆日志淹没你要找的日志吧……对吧?对吧?!)
  • BlockState JSON 中现在不再自动在模型路径中补上 block 目录了。
    • 也就是说:在 BlockState JSON 中,你之前系的 foo:bar 会指向 foo:models/block/bar,现在它只指向 foo:models/bar。你必须明确声明 foo:block/bar
    • 没错,这个也可以自动完成,抱怨无效。
    • 好处:你可以采用更灵活的方式整理方块模型了。
  • 自定义 ItemMeshDefinition 已移除,请直接使用自定义 IBakedModel 加自定义 ItemOverrideList
  • LWJGL 更新到 3.x。你现在终于可以使用过去两年多以来我们错过的 LWJGL 3 新特性了。
    • Mojang 停止使用 Java 的 BufferedImage 改用 LWJGL 3 的 stbimage binding 了。理论上不会影响大多数 Modder。
    • Keyboard.KEY_FOO -> GLFW.GLFW_KEY_FOO


语言文件的变化

  • 语言文件现在改用 JSON 格式了。之前的 key=value 变成了一个包含有大量 String 节点的 JSON Object(形如 {"block.minecraft:dirt": "Dirt"}
  • 默认,方块和物品的 translation key 将使用其注册名,冒号 : 换为 .,同时加上前缀以区分;方块所用的前缀从之前的 "tile" 变成了 "block"。举例:block.minecraft:dirtitem.minecraft:diamond
    • 有鉴于扁平化,这个不太好用脚本自动化,但要说能不能做?还是可以的。
  • ".name" 的后缀消失了。

数据包

  • 把你所有的进度、函数、战利品表、配方、以及结构等全部从 assets/ 目录移到 data/ 目录。
  • assets/ 目录又一次地成为所有只有客户端所需要资源的存放地。所有服务端需要的东西都应该通通放在 data/ 目录。
  • 看起来将会有一些 util method 用于遍历 data/ 目录下的任一指定子目录,同时支持数据包的层叠和覆盖,是个很有用的特性。笔者认为你甚至可以把你所有机器的配方都放在这里,然后你就能自动享用层叠资源加载的所有好处了。
    • 那个方法叫 IResourceManager.getAllResourceLocations
    • 重要提示:如果你真的把你的游戏机制相关文件(比如机器的配方)搬到这里了,请务必小心命名空间撞车。
    • 举例:Mojang 已经使用 data/<domain>/recipes 存放所有原版的 IRecipe 了,以及 data/<domain>/structures 则用于存放所有结构的 NBT 数据。你的子目录可能需要用你的 modid 再次明确区分。比如 data/botania/botania/petal_apothecary/ data/projecte/projecte_emc_values 这样的,如此做其他 Add-on 便可以放心使用 data/<domain>/botania/petal_apothecary/data/my_modpack/projecte_emc_values
    • 如果你不慎把你的机器合成丢进了 data/yourmod/recipes,原版 Minecraft 会尝试把它们当原版工作台或熔炉配方加载。
  • 参见 https://minecraft-zh.gamepedia.com/%E6%95%B0%E6%8D%AE%E5%8C%85 以了解更多相关信息。

元数据(Metadata)怎么了?

  • 元数据没了。全部移除了。再也不用折腾幻数(Magic Number)了!
  • 之前所有需要用元数据表达的信息,现在都可能不再需要(方块)、展开/扁平化(方块、物品)或者改由 NBT 存储(工具的耐久)。
  • 请不要再为浪费方块或物品 ID 而害怕了。理论上,它们没有上限了。(实际上还是有上限的:你分配给游戏的内存究竟能存储多少 BlockItem 的实例。答案是:百万起步。实际上这些实例占用的内存也不多。)当然,确实还有一些需要以某种变体存在(译注:IBlockState 或 NBT)的方块或物品(或者以 TileEntity 里的一个字段呈现),此时请根据实际情况做最合理的判断。如果可能,优先采取扁平化策略,注册多个方块,以及切记不要犯傻。如果不确定,可以前往 Discord 或者 #minecraftforge 寻求有经验的 Modder 的帮助。(译注:Discord 是一个即时语音通信软件,这里指的是各种 modder 聚集的 Discord 服务器;#minecraftforge 是指 esper.net 上的 #minecraftforge 这个 IRC 频道。前往这两个地方均需要相当的英语交流水平。)
  • 另外,如果还是不确定,请参考原版 Wiki 的相关页面,这样可以对哪些方块需要拆分,哪些方块仍需要保持一个方块下的不同方块状态有个概念。
    • 在你问之前先回答:你不能把 500 个有十余种可能的值属性塞进一个方块状态里,因为游戏启动时会生成所有可能的方块状态,你这样做会直接耗尽所有内存。(译注:原文是 dozen values,不一定是实指 12 种可能的值,但即便如此,12500 依然约等于 3.89E+539,试想 3 后面有 539 个零是什么数量级?)如果你需要某种动态的数据,请使用 TileEntity。
  • 使用更多的 ID 代表你不能复用代码。你完全可以写一个类,然后创建多个这个类的实例,然后以不同的名字注册。

处理物品的元数据

  • 对于工具的耐久,请将其移动到 ItemStack 的 NBT 标签下。
    • 可通过 Item 类的 getDamagesetDamage 自动完成。
  • 否则,请把物品展开,并把其中的每一个子物品都分别分配不同的 ID。
    • 例如:使用 botania:mana_diamondbotania:mana_pearl 取代 botania:manaresource @ <arbitrary magic number>
    • 如果你的代码里并没有为一个集中的物品分配成千上万个 ID 的话,那听起来不错,你几乎不需要做什么(例如:Pam's HarvestCraft Mod 已经为其所有物品分配了不同的 ID,而不是使用元数据——可以说是提前完成了扁平化)
    • 把特定字段分离,因为比较物品元数据的行为应该被替换成只对物品实例进行 == 操作符比较。
    • 旧版本的 == 操作符比较方式应该被替换成基于标签的比较(见本文的稍后部分),或者如果合适的话,可以使用 instanceof 运算符。
    • 更新你的配方和战利品表
    • 更新你的语言文件
  • 不要直接通过给物品添加 NBT 标签解决问题。除非真的很有必要(比如说同一类型物品可能的数量接近无穷,这种情况你早就应该使用NBT了)。(译注:参考 Tinker's Construct 的工具的情况。)
  • 多出来一个 ID 的代价仅仅是多出来一个 Item 实例(多分配几个字节,而且只分配一次),以及字符串 ID(为相应的字符串多分配几个字节)。
  • 如果你使用 NBT hack,那么相应的代价就是每个物品都会附带上 NBT 标签(浪费存储空间,不必要的程序执行开销,以及多余的网络带宽占用)。同步一个普通 ItemStack 只需要两个 int(数字 ID 和数量)(译注:实际上消耗比这还要低,数字 ID 是使用 varint 同步的,意味着物品数字 ID 的实际占用可能不是 32 bit 而是 16 bit 甚至 8 bit!)。使用 NBT 标签会直接导致处理开销直线提升(译注:来自完整序列化 NBT 的开销),而且这个开销对于每个这样的 ItemStack 都有,且实际上这个开销叠加起来非常快。
  • 另外,大量原版或 Mod 的相关系统不会(在笔者看来也不应该)支持 NBT,尤其是标签系统。NBT 只应是例外,而不应是常态。
  • 不,你以为你已经在用 GET/SETITEMDAMAGE 而且通过编译了并不代表你就可以不管了。麻烦正确扁平化你的物品,不然小心我顺着网线找到你。

处理方块的元数据

这一段将以最简练的语言描述,请确认你对方块状态有明确的认识:

  • 所有原版的 IBlockState 都将完全保存(没错,这还包括那些像是栅栏连接状态这种在 getActualState 里即时计算出来的属性,1.13 之前这些都不保存)。
    • 栅栏等有连接逻辑的方块使用 Block.func_196242_cupdateNeighbors)和 Block.func_196271_a(目前为 updatePostPlacement)处理相关逻辑,参考 MCP 注释获取详细信息。
    • 对于不需要存储在服务器端的渲染用属性,应使用 Forge 的扩展方块状态(译注:IExtendedBlockState),但请注意现在有一个策划中的替代方案,目前扩展方块状态仍无法工作。
  • 请直接删除 getMetaFromStategetStateFromMeta 两个方法,因为已经不需要了。希望你在 1.8 的迁移时没走捷径……
  • 扁平化同时意味着所有可能需要独立的 ItemBlock 的东西,现在都需要变成一个独立的方块。从原方块继承的属性应保留。
    • 举例:原版的原木不再全部挤在 minecraft:log[axis, variant] 里,现在是 minecraft:oak_log[axis=x, y, z]minecraft:oak_bark 这样的。原木的朝向不需要再做拆分(因为朝向完全取决于放置的位置),但不同种类的原木应当拆分(因为物品没有元数据了)。
    • 最简单的处理方法是,对于所有用来区别种类的 PropertyEnum,直接将对应的枚举类实例传入方块的构造器中,由方块持有这个实例。这样一来就可以让检查实例取代检查方块状态。原版的处理方式可参考 BlockShulkerBox:每一个它的实例中都有一个 EnumDyeColor 的字段。
  • 同样的,根据实际情况,拆分所有的 ItemBlock。理论上,这个过程应该只是用扁平化后的方块创建更多同一个类的实例。
    • 请不要使用 NBT,理由上面已经阐述过了。
  • 根据实际情况,对不同变体的比较应当全部简化为对两个方块的实例使用 == 操作符。
  • 如果你此前在用注册名和元数据的组合来存储方块状态,现在你应该使用 NBTUtil::readBlockstateNBTUtil::writeBlockstate 了。可参考末影人如何保存它当前携带的方块。

标签系统(Forge 矿物辞典相关内容请直接 Ctrl+F)

(译注:Ctrl+F 这里是指替换删除解决问题)

  • 请阅读 https://mcforge.readthedocs.io/en/1.13.x/utilities/tags/
  • 请记住:方块标签(tags/blocks/...)在原版中只用于类似 /execute 这样会直接查询世界里的方块的命令。进度、合成配方等使用的是 ItemBlock,它们是物品,所以它们使用物品标签(tags/items/...)。没错这会产生大量重复标签。如果你懒,可以不要方块标签,但这里仍然推荐加上单独的方块标签以提供更好的兼容性。
    • 举个例子,像是 Botania 这种有在世界中直接操作方块的加工配方的 Mod 可能会用到方块标签。(译注:指 Botania 的白雏菊。)
    • 在 1.13 之前有类似操作的 Mod 都会通过遍历矿物辞典这种非常慢,而且是针对物品设计的方式,辅以缓存来完成这个任务。方块标签使得这些 Mod 可以进行常数级(译注:大 Theta(1))的集合查询。


配方

  • 你现在要给每一个配方写一个 JSON。(前排售卖瓜子汽水板凳橘子火车站等)
    • 其实这样说不准确。更准确地说:你需要一个 JSON 以确保它能通过数据包禁用(以及同步至客户端)。
    • 简单来说,1.13 中有一个配方类型到 JSON 反序列化器的注册表,所以你的 JSON 配方,包括实际的代码在内,实际上可以是任意格式的了。(想想看战利品表、LootFunctions 和 LootCondition 是什么情况)。
    • 参考原版的 mapcloning.jsonmapextending.json 及其对应的代码实现,JSON 文件只指明了配方类型,整个合成的逻辑都是用代码实现的。
    • 对于一般合成,请使用 JSON。
  • TBD:Mojang 似乎有一个自动合成导出工具(net.minecraft.data.RecipeProvider),可生成所有的配方 JSON 文件及一个解锁配方所需要的进度。这个工具看上去只需要在运行 Minecraft 的 jar 时指定一个不一样的入口方法就能用了。
  • 熔炉配方也用 JSON 了。

高级配方

  • 你现在可以为配方注册特定类型的反序列化器了,使用何种反序列化器将由配方 JSON 中的 type 字段决定。
  • 你的配方类型必须在客户端和服务端之间同步(因为配方将会在玩家登录或数据包重新加载时同步到客户端)。
    • 不,不要仅仅是把 JSON 字符串同步过去。它们将会在玩家登录时进行数据同步,因此你不应该让客户端浪费时间在重新解析这种东西上。理论上,你应需要同步的数据压缩到极限。
  • 一对多或者超级动态化的配方类型仍然没有实现好,如果你想要参与讨论的话,你可以找 Forge 开发者或者感兴趣的 Modder(tterrag)

命令

  • 命令系统重写,现使用 Mojang 的一个 Brigadier 库(无混淆,你可以直接看它源码)。(译注:源码在 https://github.com/Mojang/brigadier 这个仓库。)
  • 命令相关的语法更宣告式(declarative)了,在此之前你是手动解析命令。
  • 命令的语法现在会从服务器同步到客户端。
  • Tab 自动补全只会在命令允许此参数自动补全的时候询问服务器补全结果。
  • 有静态可预知结果的自动补全是在客户端完成的(比如 /gamemode 的参数补全)。
  • 暂时没有更多细节,请参考原版。

流体与 waterlogged 方块

  • 1.13 加入全新的“水淹方块”的机制。
  • 这套接口非常通用——现在 World 下有 getFluidStatesetFluidState 方法了,流体的处理则和方块几乎是一个等级的(它们甚至可以有自己的 IProperty)。
  • 然而,目前的流体状态(fluid state)的实现只是把相关调用代理回方块状态下的 waterlogged 属性。
    • 1.13.x 中的这套系统看样子是套半成品,敬请期待它进化到类似基岩版中水可以淹没任意方块的版本。
  • 更多信息还需要等待进一步分析。
  • 流体现在是原版 Minecraft 中需要注册的对象之一了。你所有类似 == Blocks.WATER==Blocks.LAVA 这样的检查应该换成基于标签的检查(参考 `BlockCoral.canLive)。

世界生成变化

  • 巨大更新,可能是这次更新中变动最大的系统。
  • 现在有专门的世界生成线程。笔者在此希望你的世界生成相关方法都是纯函数。
    • 译注:纯函数是指没有“副作用”,且相同输入一定会产生相同输出的函数,即调用它不会影响外界其他对象的状态,且反复以相同参数调用不会产生不同的返回值。举例,Math.sinMath.lognew ResourceLocation() 这些都是纯函数,而 PrintStream.println/System.out.println、NIO 的 Files.deleteWorld.setBlockState 这些都不是纯函数,即 impure function。这里提到纯函数是因为纯函数与生俱来的与多线程的相容性。
    • 看不懂?没关系这个 joke 其实很偏。https://zh.wikipedia.org/wiki/%E7%BA%AF%E5%87%BD%E6%95%B0
  • TBD 等待 MCP 完成相关命名,但不论如何你都得准备好大改代码了。
  • Barteks 有一个 WIP 的指南:https://gist.github.com/Barteks2x/41122efc766afdd47aeb457a3c19b275(译注:暂缓开坑,说不定明天就翻译出来了)

DataFxier


杂项

  • 原版现在有了一个“根据你的视角产生不同的 AABB”的系统(换言之,原版实现了一个更好的 Diet Hopper)。不知道我在说什么的话,请去玩玩 1.13 快照中的漏斗。具体实现请参考漏斗和末地传送门框架这两个类。
  • 结构方块(以及任何你用到它的东西)都需要在 1.12 版本的存档中重新加载一遍,然后让这个存档跑在 1.13 里,然后再在 1.13 中保存相关文件。虽然不是很清楚 Mojang 为什么不弄个自动化修复工具,但现在就是这样做。
  • 像是材质(译注:Material.GROUNDMaterial.ROCK 这样的)、硬度等方块的属性(attributes)现在需要通过一个 builder/POJO 来设定,然后把这个 POJO 传入方块构造器,在 Block 类下相关字段均为 final
    • 物品同理。
  • 方块没有创造标签页了,那是 ItemBlock 那个物品的职责。
  • 生物群系 ID 现在以 int 保存(以前是 byte),使得生物群系最大 ID 从 255 直接提高到 20 亿(译注:精确值 Integer.MAX_VALUE == 2147483647)。
  • 附魔不以数字 ID 存储了,改以完整注册名存储(早该这样了)
  • 关于数字 ID 的一句话:
    • 对于任何方块和物品,你现在都应该全面使用注册名了。原版保存注册名到整型 ID 的表的唯一原因是性能,因此除了网络连接等对性能有要求的情况外都不应该使用数字 ID,因为 Minecraft 完全不保证对于不同的世界数字 ID 仍然能保持不变。1.8 起这一点应该已经成为 modding 的传统了,但还是有人不按规矩来,所以有必要再重复一次 :P
  • World 现在实现了一堆接口,每一个接口的职责不尽相同(比如有一个代表“只读”的“世界”的接口)。若条件允许,请使用最不精确的一个。
  • 基本上所有可命名的东西都改用文本组件(text component)了,所以——翻译一切!
  • 有不止一种空气方块了(airvoid_aircave_air),请把所有的 == Blocks.AIR 替换为 IBlockState.isAir
  • 更多的对象开始走注册表了:实体、BiomeProviderChunkGeneratorParticleTypeStatsPaintings
  • 和往常一样,请检查对原版 1.13.x 的 patch 以确保笔者没漏掉任何和游戏体验相关的东西。

乱七八糟的事实(善用 Ctrl+F)

  • SideOnly(Side.CLIENT) -> OnlyIn(Dist.CLIENT)
  • SidedProxy -> DistExecutor
    • 举例:public static IProxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> ServerProxy::new);
    • 为什么多一层 lambda?为了防止 ClientProxy 和 ServerProxy 直接通过 Mod 主类引用触发加载。
  • mcmod.info 改为 mods.toml,参考 MDK 的示例 Mod 里的 src/main/resources/META-INF/mods.toml
  • EventBus 现在是单独的库了(按情况修复导入)。可直接绕开注解注册事件处理方法(比方说这样你就能使用 private 了)。参考 MDK 的示例 Mod。
  • Loader.isModLoaded -> ModList.get().isLoaded()
  • 几个重要的 MCP/Forge 重命名
    • Entity.onUpdate -> tick
    • Entity.onEntityUpdate -> basetick
    • Entity.setDead -> remove
    • World.setBlockToAir -> removeBlock
    • World.scheduleUpdate -> world.getPendingBlockTicks().scheduleTick(方块和流体的 ticking 现在是分开的。)
    • 方块方法:所有 Forge patch 进去的方法中,IBlockState 参数移动到最开头。
  • 检查物理端:FMLEnvironment.dist
  • 检查逻辑端:
    • getEffectiveSide -> EffectiveSide.get强烈建议改用 World.isRemote检查!)
    • 获取逻辑服务器:ServerLifecycleHooks.getCurrentServer()
  • 网络:SimpleImpl -> SimpleChannel
  • ScaledResolution.<x> -> Minecraft.getInstance().mainWindow.<x>
  • isOpaqueCube 已移除,Minecraft 现直接检查方块的 VoxelShape

其他

  • 遇到问题时,可以去 Discord 或 IRC 上咨询有经验的 Modder,或者直接看他们是怎么迁移的。
  • 请在力所能及的范围内帮助他人们,自己若不清楚不要去瞎帮忙。一如既往,若遇到问题,请参考原版是怎么做的。

勘误

(译注:只翻译了原标题中的 erratus, -a, -um,拉丁语原意大致为“mistaken”,英语中指印刷中的疏漏)

  • 如果你发现了本指南中的疏漏之处,请及时通过 Modded Minecraft Discord、推特或者 #minecraftforge IRC 频道通知我(williewillus)。我不希望看到错误的信息流传开来,所以你的及时通知就是对我最好的帮助。





2018 年 1 月 19 号翻译版本: