简体中文
内容注册
内容注册分两条轨: 代码轨(ContentRegistry,本页)与 数据轨(TOML 内容包,见文末)。 两者共用同一条管线:声明在启动期收集,在 注册表冻结点一次性材料化为原生内容,之后与 vanilla 内容不可区分。
注册表冻结
Minecraft 的注册表在启动早期冻结,冻结后不可增删。OML 在冻结点把声明的材料化进去——这之后 (包括运行期)都不能再新增内容。内容声明请写在 declareContent 内。
declareContent
@Mod 类额外实现 OMLContentProvider,加载器会在游戏主逻辑启动前调用它:
kotlin
@Mod(id = "my_mod", name = "My Mod", version = "1.0.0")
class MyMod : OMLModInitializer, OMLContentProvider {
override fun declareContent(registry: ContentRegistry) {
// 在这里声明
}
}registry 已绑定本 mod 的命名空间:声明里的 id 不带域名,OML 按 mod id 绑定。
方块
kotlin
val block = registry.declareBlock("ruby_ore") {
destroyTime = 3.0f
explosionResistance = 6.0f
requiresCorrectToolForDrops = true
}| 属性 | 默认 | 说明 |
|---|---|---|
destroyTime | vanilla 默认 | 硬度(破坏耗时) |
explosionResistance | 跟随 destroyTime | 爆炸抗性 |
requiresCorrectToolForDrops | false | 是否需要正确工具才掉落 |
每个方块自动注册对应的方块物品。返回的 OMLBlock 在内容注册完成后可访问 platform(原生方块对象)—— 需要版本特定行为时的逃生舱。
行为钩子
方块可以携带声明式行为钩子: 行为实现由版本 adapter 提供(它材料化自己的 Block 子类),mod 这边 只写普通 lambda。原版行为始终照常执行——行为方块就是"普通方块 + 钩子"。
kotlin
registry.declareBlock("trap_floor") {
onStepOn { event ->
if (!event.isClient) {
// 服务端:施加伤害、效果——需要时经 event.level / event.entity 逃生舱
}
}
onHit { event ->
// 玩家刚开始挖掘这个方块
}
}| 钩子 | 触发时机 | 事件 |
|---|---|---|
onStepOn | 有实体站在方块上的每个 tick,双端都会触发 | OMLStepOnEvent —— x/y/z、isClient、level、entity |
onHit | 玩家开始破坏方块 | OMLBlockHitEvent —— x/y/z、isClient、level、player |
level 与 entity/player 是原生版本对象——与 platform 同一条逃生舱。玩法效果请以 isClient 为闸门:钩子双端都会触发。
方块实体:机器
blockEntity 给方块一个服务端 tick 和一个 持久化数据存储——机器就是这两块拼出来的:
kotlin
registry.declareBlock("press") {
blockEntity {
tick { event ->
val progress = event.data.getInt("progress") + 1
event.data.putInt("progress", progress) // 随存档持久化
}
}
}tick在区块加载期间每个游戏 tick 运行一次, 仅服务端(客户端收不到该事件——机器 tick 是 模拟概念)。event.data是OMLBlockData:基础类型键值对(int/long/float/double/boolean/string),随世界持久化;写入会标记方块实体等待下次自动保存。- 行为钩子与方块实体可以自由组合在同一个方块上。
世界生成:矿石
generateAsOre 让方块作为矿脉自然生成。它材料化为数据包 worldgen 文件并合并进目标生物群系—— 与任何数据包矿石一样, 只影响新生成的区块:
kotlin
registry.declareBlock("ruby_ore") {
destroyTime = 3.0f
requiresCorrectToolForDrops = true
generateAsOre {
veinSize = 8 // 每条矿脉的方块数
perChunk = 6 // 每区块的尝试次数
minY = 16
maxY = 64 // 梯形高度分布
biomes += listOf("minecraft:forest", "minecraft:taiga") // 留空 = 所有生物群系
}
}矿脉替换石器家族(minecraft:stone_ore_replaceables);biomes 留空在下界/末地同样安全—— 下界岩与末地石不匹配替换标签。
物品
kotlin
registry.declareItem("ruby_sword") {
maxDamage = 592
attackDamage = 7.0
attackSpeed = -2.4 // vanilla 基础 4.0 → 实际 1.6
miningSpeed = 1.0f
toolDamagePerBlock = 2
minesAndDrops("minecraft:stone", 8.0f) // 对石头:挖掘速度 8 且掉落
overrideSpeed("minecraft:dirt", 4.0f) // 对泥土:挖掘速度 4,掉落同 vanilla
deniesDrops("minecraft:bedrock") // 永不掉落
}| 属性 | 默认 | 说明 |
|---|---|---|
maxDamage | 无(不可损坏) | 最大耐久,>0 时显示耐久条 |
attackDamage | 无 | 主手攻击伤害 加成值(vanilla 空手 1.0) |
attackSpeed | 无 | 主手攻击速度 加成值(vanilla 基础 4.0) |
miningSpeed | – | 无匹配规则时的默认挖掘速度(需有工具规则) |
toolDamagePerBlock | 1 | 每破坏一个方块的耐久消耗 |
canDestroyBlocksInCreative | true | 创造模式能否瞬间破坏 |
工具规则的语义:minesAndDrops = 匹配方块按指定速度挖掘 且掉落战利品;overrideSpeed = 只改速度、 掉落同 vanilla;deniesDrops = vanilla 速度且永不掉落。这些映射到原生的 DataComponents.TOOL。
配方与掉落
合成
kotlin
// 有序合成:4 个 ruby 合成 1 个 ruby_block(pattern 行引用 key 映射,' ' 为空格)
registry.declareShapedCrafting(
result = "my_mod:ruby_block",
pattern = listOf("RR", "RR"),
key = mapOf('R' to "my_mod:ruby"),
)
// 无序合成:材料任意摆放
registry.declareShapelessCrafting(
result = "my_mod:ruby",
ingredients = listOf("my_mod:ruby_ore", "minecraft:stick"),
count = 2,
)pattern 规则:1-3 行、每行 1-3 格、各行等宽、每个非空格字符必须有 key 条目——格式错误在声明时 直接抛出,早于内容包加载。裸材料 id 自动补上 mod 命名空间。数据包文件 id 由 result 派生 (my_mod:ruby_block → my_mod_ruby_block);两个配方产出相同时,第二个加数字后缀。
熔炼
kotlin
// 熔炼(furnace 可选 SMELTING / BLASTING / SMOKING,默认 SMELTING)
registry.declareSmelting(
input = "minecraft:raw_iron",
result = "minecraft:iron_ingot",
experience = 0.7,
cookingTime = 200,
)
// 方块掉落改写:破坏 ruby_ore 掉 1~3 个 ruby
registry.declareBlockDrop(block = "ruby_ore", drop = "my_mod:ruby", dropCountMin = 1, dropCountMax = 3)配方与掉落表材料化为数据包 JSON,经 OML 注入的资源包走 vanilla 自己的数据包重载路径—— 服务端权威、自动同步到客户端。
数据轨:TOML 内容包
不需要写代码的内容:声明写在 .oml 归档根目录的 content.toml(普通 zip 改名,目录结构见下方 "分发形态")。归档名即命名空间,包内可声明方块、物品与配方:
toml
[block.ruby_ore]
destroy_time = 3.0
explosion_resistance = 6.0
requires_correct_tool = true
[item.ruby]
max_damage = 64
attack_damage = 2.0
# 工具规则是内联表数组,每个方块一项。
mines_and_drops = [{ block = "minecraft:stone", speed = 8.0 }]
override_speed = [{ block = "minecraft:dirt", speed = 4.0 }]
[crafting.ruby_block]
type = "shaped"
pattern = ["RR", "RR"]
key = { R = "ruby" }
[crafting.ruby_from_ore]
type = "shapeless"
count = 2
ingredients = ["ruby_ore", "minecraft:stick"]
[smelting.ruby]
input = "raw_ruby"
experience = 0.7
cooking_time = 100
# furnace = "blasting" # smelting(默认)/ blasting / smoking
[loot.ruby_ore]
drop = "raw_ruby"
drop_count_min = 1
drop_count_max = 3字段与代码 API 一一对应(destroy_time → destroyTime …);mines_and_drops 与 override_speed 的条目带 block 与 speed,denies_drops 的条目只带 block。[crafting.<产出物 id>] 段声明一个合成配方:type 选择 shaped / shapeless;shaped 需要 pattern(行)与 key 内联表(图案字符 → 物品 id);shapeless 需要 ingredients 列表;count 可选(默认 1)。[smelting.<产出物 id>] 段声明熔炼配方(input 必填, furnace 选择 smelting / blasting / smoking,experience 与 cooking_time 可选);[loot.<方块 id>] 段声明破坏该方块改为掉落 drop。方块段里的 ore 内联表对应 generateAsOre(vein_size、 per_chunk、min_y、max_y、biomes)。格式错误(图案不齐、未知类型、缺字段)会让内容包加载失败 并说明原因。TOML 包与代码 mod 共享同一条 收集 → 冻结材料化管线,资产注入也把 TOML 包的命名空间当作 mod 域对待。字段拼错会在启动日志里 被逐条点名——不存在静默忽略。数据轨 不覆盖:行为钩子与方块实体是 mod 代码,没有 TOML 形态。
散装 .toml 文件丢进 mods/ 不是受支持的包形态:它无法携带自己的贴图,方块会渲染成缺失材质, 因此 loader 会明确报错并拒绝加载。请打成 .oml 归档。
分发形态:.oml 归档
内容包以 .oml 归档分发(普通 zip 改名):根目录放 content.toml,随包携带自己的 assets/ 与 data/:
MyPack.oml (zip)
├── content.toml
└── assets/
└── my_pack/
├── textures/block/ruby_ore.png
└── lang/en_us.json丢进 mods/。归档名即命名空间;归档内的贴图与 lang 经 OML 注入的资源包对外服务。
热重载
内容轨的配方跟随 vanilla 的数据包重载:
- 贴图 / lang / 模型:
F3+T会重新扫描 mod jar 与.oml归档,改动的文件当场生效。 - 合成 / 熔炼配方与掉落表:修改包内声明后执行
/reload(或F3+T),loader 从磁盘重新读取内容包, 数据包 JSON 以最新内容应答。矿石 worldgen 文件同样随重载刷新,但世界生成本身只作用于新生成的区块。 - 方块 / 物品本体:不可热重载。注册表在启动早期冻结,新增或修改方块 / 物品需要重启—— 这是 vanilla 的约束,不是 OML 的限制。