
Patchly
完美的纯净打补丁插件。一款现代化的就地Zima与Hytalor替代方案。
Patchly - 纯补丁插件
面向模组开发者,请使用 curse.maven:patchly-1550889:8406981
一种原地替换的现代化 Zima 和 Hytalor 替代方案
完整文档位于 https://wiki.hytalemodding.dev/mod/patchly
*虽然 Patchly 与另外两个补丁解决方案 Hytalor 和 Zima 兼容,但不建议同时安装三者。在这种情况下,不保证其可靠性。Patchly 拥有另外两个方案的 100% 功能,因此首选迁移到单一补丁解决方案。*
无需从头重写即可对 JSON 资产进行补丁。零依赖。
Patchly 的诞生原因
Hytale 原生的 Parent: super 仅在外部资产层级继承——大多数嵌套的编解码器字段(例如 Item.armor.StatModifiers、DamageResistance)使用的是 .append(...) 而非 .appendInherited(...),因此常规的 JSON 重写会替换整个子对象,并静默清除所有未重新声明的部分。
Patchly 的作用
Patchly 会读取已解析的基础资产,并将你的 .patch 文件深度合并到其上,因此你只需编写差异部分。
适用于所有已注册的 AssetPack——包括文件夹、.zip 和 .jar 包。仅需 JSON 的模组制作者可以将 Patchly.jar 放入 mods/ 文件夹中;Java 模组制作者可以通过 Gradle Shadow 将 Patchly 打包到自己的 jar 中。Patchly 会与其他 Patchly 实例协调,确保同一时刻只有一个 Patchly 在运行。这意味着你的模组可以独立运行,也可以与其他 Patchly 模组、patchly.jar 等协同工作。
补丁的工作原理
将 .patch 文件放置在与你要打补丁的资产相同的路径下,将 .json 扩展名替换为 .patch。要为其他包中的 Armor_Iron_Head.json 打补丁,请在您的包中提供 Server/Item/Items/Armor/Iron/Armor_Iron_Head.patch。
Patchly 遍历每个包,解析每个目标的最新版本,将你的 .patch 合并到其上,并将结果写入一个优先级更高的合成覆写包中。
最小补丁
{
"Armor": {
"StatModifiers": {
"Mana": [{ "Amount": 126, "CalculationType": "Additive" }]
}
}
}
按字段深度合并。Mana 会被放入父级现有的 StatModifiers 块中;Health 和其他同级别属性保持不变。
数组的替换与追加
默认情况下,数组会被替换。在键名后添加 + 后缀可改为追加:
{
"BlockType": {
"Bench": {
"Categories+": [
{ "Id": "Arcane_Hexcode", "Icon": "...", "Name": "..." }
]
}
}
}
父级现有的 Categories 条目会保留;此条目会被添加到末尾。
数组的前置
在键名后添加 - 后缀可将你的条目添加到前面而非末尾:
{
"Children-": [
{ "Id": "Hexcode", "Name": "hexcode.itemcategory.hexcode.name" }
]
}
你的条目会按照你编写的顺序放置在父级条目的前面。- 与 + 互相对称(如果数组不存在则创建,支持 $Match)。它表示前置,而非删除。
仅在键缺失时填充
在键名后添加 ? 后缀,仅在目标未定义该键时写入值。如果键已存在,则基础值胜出,你的值被丢弃:
{
"Armor": {
"StatModifiers": {
"Mana?": [{ "Amount": 200, "CalculationType": "Additive" }]
}
}
}
已经拥有 Mana 的物品会保留它;没有的物品则会获得 200。这是基于存在性的判断,因此适用于任何值类型,并且按每个键独立决策。这是让基础值获胜的唯一方式——$Priority 仅用于决定各个补丁之间的顺序,从不与基础值比较。
$Requires - 仅在安装了特定包时应用
单个包:
{
"$Requires": "Riprod:Hexcode",
"Armor": { ... }
}
多个包(必须全部存在):
{
"$Requires": ["Riprod:Hexcode", "Author:SomeOtherPack:^0.5.0"],
"Armor": { ... }
}
包 ID 会与 AssetPack.getName() 进行匹配(即来自目标模组清单的 Group:Name)。如果缺少任何包,则该补丁会被跳过并记录一条日志。
目前不支持排除包。我们认为这并非必要,如果你希望有此功能,欢迎提交 Pull Request。
$Priority - 解决冲突时选择胜者
整数,默认值为 0。数值越小越先应用,数值越大越后应用 → 在字段冲突中数值大的胜出。平局时由包的加载顺序决定。
{
"$Priority": 100,
"Armor": {
"StatModifiers": {
"Mana": [{ "Amount": 9999, "CalculationType": "Additive" }]
}
}
}
两个模组对同一字段打补丁时都会应用,但 $Priority 更高的那个会在最后写入。较低优先级的 + 追加操作仍然会叠加到较高补丁未触碰的字段上。
保留键
任何以 $ 为前缀的顶层键都是元数据——在合并前会被剥离,不会进入合成的资产中。目前具有语意的只有 $Requires 和 $Priority;$Comment(或任何其他 $Foo 格式的键)可自由用于你个人的备注。
使用方法
作为独立工具(资产包)
将 Patchly-X.Y.Z.jar 放入服务器的 mods/ 文件夹中,与你的资产包放在一起。就这么简单。Patchly 将进行扫描、合并、注册。
你也可以在 package.json 中将其添加为必要依赖,并在 CurseForge 上将其添加为关联项目,以提高可见度!
作为捆绑依赖(Java 模组)
添加 Shadow 插件并依赖 lib jar:
plugins {
id("hytale-mod") version "0.+"
id("com.gradleup.shadow") version "8.3.5"
}
// 一个专用配置,使得只有 Patchly 被打包,而不是你的编译依赖
val shaded by configurations.creating
dependencies {
// 针对 API 编译,并标记为将其打包到最终 jar 中
shaded(files("deps/Patchly-3.1.1.jar"))
implementation(files("deps/Patchly-3.1.0.jar"))
}
tasks.shadowJar {
archiveClassifier.set("") // shadow jar 就是发布的构件
mergeServiceFiles()
configurations = listOf(shaded) // 仅打包 `shaded` 中的内容
relocate("com.riprod.patchly", "com.riprod.<你的包ID>.shaded.patchly")
}
tasks.jar { enabled = false } // 禁用瘦 jar
tasks.build { dependsOn(tasks.shadowJar) }
然后在你的 JavaPlugin 中:
import com.riprod.patchly.PatchManager;
public final class MyPlugin extends JavaPlugin {
private final PatchManager patchManager;
public MyPlugin(JavaPluginInit init) {
super(init);
patchManager = new PatchManager(this);
}
@Override protected void setup() {
patchManager.install();
}
}
注意:在使用
./gradlew runServer进行测试时,你不能同时将 Patchly.jar 放在./mods文件夹和作为依赖。这是因为开发服务器会扫描你的依赖,找到你的 Patchly.jar 的 manifest.json,并将其也注册为自己的包。如果它同时也存在于./mods/中,那么它就会被注册两次并导致崩溃。反正你也不需要将 Patchly.jar 放在你的mods/文件夹中,所以直接删掉它就行。
如果在同一个 JVM 中同时安装了独立的 Patchly.jar 和一个捆绑了 Patchly 的模组,每个实例都会投出自己的版本,最新的版本会成为唯一的活跃拥有者;较旧的版本会退出并变为空操作。该决定在启动时最终确定。没有重复工作,没有冲突。
注意事项
- 热重载适用于文件夹包模组中的
.patch文件(jar/zip 包在注册时一次性应用;没有实时重载)。 - 输出位于
MODS_PATH/<群组>_<名称>_PatcherOverrides/目录中,每次冷启动时都会被清空。 - 启动时出现
[AssetModule] Skipping pack at ..._PatcherOverrides: missing or invalid manifest.json是良性的——合成包是通过编程方式注册的,而非文件系统扫描。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。