Skip to content
On this page

注册

向游戏添加自定义内容是模组的核心。Katton 让你能在 Kotlin 脚本中注册原生 Minecraft 对象,并在 Fabric/NeoForge 上支持热重载。

WARNING

注册系统仍在积极开发中。RELOADABLE 模式的 API 已经稳定,但未来可能还会有调整。如有疑问请参考 API 文档

IMPORTANT

Paper 禁用注册表操作。连接到 Paper 服务器的原版客户端无法接收自定义物品、方块、实体或组件注册表条目。Paper 上请使用数据包修改、Bukkit API 和事件逻辑。

注册模式

每个注册函数都接受一个 registerMode 参数:

模式行为
RegisterMode.GLOBAL模组初始化时注册一次。纳入重载追踪。所有重载都保留。
RegisterMode.WORLD只会在进入世界的时候被加载。在世界内的重载不会影响此注册。当退出世界以后注册信息被清除。
RegisterMode.RELOADABLE由 Katton 追踪。重载时所有者信息被清空,脚本可重新注册。Minecraft 注册表条目会被保留(软保留)以防止 holder 崩溃。

TIP

迭代中的内容用 RELOADABLE。需要跨重载保持不变的内容用 GLOBAL

重载生命周期

执行 /katton reload 后:

  1. 每个注册表调用 beginReload() — 清除所有权追踪
  2. 脚本重新执行 — ensureRegistered 如果已注册则返回同一个实例
  3. markManaged() 重新标记为当前脚本所有
  4. 残留条目(不再被任何脚本注册的)保留在 Minecraft 注册表中直到重启

两个逻辑侧都要执行注册

Fabric/NeoForge 的内置内容由服务端在 ServerPhase.READY 注册,客户端则必须在 ClientPhase.REGISTRY_SETUP 注册相同 ID。因此下面的示例会在同一个无参函数上放置两个注解;客户端阶段会在多人注册表校验前执行。

命令仅在服务端注册,只使用 ServerPhase.READY。Paper 会禁用本页全部注册表示例。

注册诊断

/katton registry 查看每个注册表的摘要:Katton 追踪了多少条目、有多少被脚本管理、有多少是残留的。

/katton registry stale 只显示有残留条目的注册表。

全部原生 API

1. 物品

最常用的注册调用。可以注册简单物品、自定义行为物品和食物。

kotlin
import net.minecraft.network.chat.Component
import net.minecraft.resources.Identifier
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.food.FoodProperties
import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.dpcaller.tell
import top.katton.api.registry.registerNativeItem
import top.katton.registry.RegisterMode

// Built-in registry entries must be created on both logical sides.
@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun registerHelloItem() {
    registerNativeItem(
        id = "qwq:hello",
        registerMode = RegisterMode.RELOADABLE,
        configure = {
            setName(Component.literal("Hello"))
            stacksTo(1)
            setModel(Identifier.fromNamespaceAndPath("minecraft", "diamond"))
            food(FoodProperties.Builder().nutrition(1).saturationModifier(0.1f).build())
        }
    ) {
        // The item class itself is retained by Minecraft's registry. Delegate
        // behavior to a normal function so the implementation can be reloaded.
        object : Item(it) {
            override fun use(
                level: Level,
                player: Player,
                hand: InteractionHand
            ): InteractionResult = useHelloItem(player)
        }
    }
}

fun useHelloItem(player: Player): InteractionResult {
    (player as? ServerPlayer)?.let { tell(it, "Used the hello item!") }
    return InteractionResult.SUCCESS
}

CAUTION

客户端连接服务器时只有物品数据组件会同步——物品类的 Kotlin 逻辑不会同步。需要客户端执行的交互逻辑要额外处理。

2. 方块

注册自定义方块,可设强度、工具要求、自定义行为。

kotlin
import net.minecraft.world.level.block.Block
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeBlock
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun registerTestBlock() {
    registerNativeBlock(
        id = "qwq:test_block",
        registerMode = RegisterMode.RELOADABLE
    ) { props ->
        Block(
            props
                .strength(3.0f, 6.0f)
                .requiresCorrectToolForDrops()
        )
    }
}

NOTE

方块需要资源包里的方块状态 JSON 和模型 JSON 才能正常渲染。

3. 药水效果

创建自定义药水效果——增益或负面均可。

kotlin
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.LivingEntity
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeEffect
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun registerTestEffect() {
    registerNativeEffect(
        id = "qwq:test_qwq",
        registerMode = RegisterMode.RELOADABLE
    ) {
        object : MobEffect(MobEffectCategory.BENEFICIAL, 0x55FF55) {
            override fun applyEffectTick(
                serverLevel: ServerLevel,
                mob: LivingEntity,
                amplification: Int
            ): Boolean {
                mob.hurtServer(serverLevel, mob.damageSources().wither(), 1.0F)
                return super.applyEffectTick(serverLevel, mob, amplification)
            }

            override fun shouldApplyEffectTickThisTick(
                tickCount: Int,
                amplification: Int
            ): Boolean = tickCount % 20 == 0
        }
    }
}

4. 命令

脚本注册的命令用 ScriptCommandRegistry——重载时自动清理。它不是 Minecraft 内建注册表,但采用相同的重载所有权模型。

kotlin
import com.mojang.brigadier.arguments.IntegerArgumentType.getInteger
import com.mojang.brigadier.arguments.IntegerArgumentType.integer
import com.mojang.brigadier.arguments.StringArgumentType.getString
import com.mojang.brigadier.arguments.StringArgumentType.word
import net.minecraft.commands.SharedSuggestionProvider.suggest
import net.minecraft.network.chat.Component
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.registry.registerCommand

@ServerScriptEntrypoint(ServerPhase.READY)
fun commandTest() {
    registerCommand("demo") {
        literal("ping") {
            executes { ctx ->
                ctx.source.sendSuccess(
                    { Component.literal("[demo] pong") },
                    false
                )
                1
            }
        }

        literal("echo") {
            argument("text", word()) {
                suggests { _, builder ->
                    suggest(listOf("hello", "world", "katton"), builder)
                }
                executes { ctx ->
                    val text = getString(ctx, "text")
                    ctx.source.sendSuccess(
                        { Component.literal("[demo] $text") },
                        false
                    )
                    1
                }
            }
        }

        literal("add") {
            argument("a", integer()) {
                argument("b", integer()) {
                    executes { ctx ->
                        val a = getInteger(ctx, "a")
                        val b = getInteger(ctx, "b")
                        ctx.source.sendSuccess(
                            { Component.literal("[demo] $a + $b = ${a + b}") },
                            false
                        )
                        1
                    }
                }
            }
        }
    }
}

5. 声音事件

注册自定义声音。需要资源包里搭配 sounds.json

kotlin
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeSoundEvent
import top.katton.api.registry.createVariableRangeSoundEvent
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    registerNativeSoundEvent(
        id = "mymod:my_sound",
        registerMode = RegisterMode.RELOADABLE
    ) {
        createVariableRangeSoundEvent("mymod:my_sound")
    }
}

You also need a sounds.json in your resource pack to map the sound event to an actual audio file.

6. 粒子类型

添加自定义视觉粒子。

kotlin
import net.minecraft.core.particles.SimpleParticleType
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeParticleType
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    registerNativeParticleType(
        id = "mymod:my_particle",
        registerMode = RegisterMode.RELOADABLE
    ) {
        object : SimpleParticleType(false) {}
    }
}

7. 方块实体类型

用于存储数据的方块(箱子、熔炉、自定义机器)。

kotlin
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeBlockEntityType
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    // createMachineBlockEntityType() is project support code that returns
    // a fresh, unregistered BlockEntityType for MyBlockEntity and its block.
    // Its implementation is loader-specific; the Katton registration call is shared.
    registerNativeBlockEntityType(
        id = "mymod:my_block_entity",
        registerMode = RegisterMode.RELOADABLE
    ) {
        createMachineBlockEntityType()
    }
}

8. 创造模式标签页

在创造模式背包中组织你的物品。

kotlin
import net.minecraft.network.chat.Component
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeCreativeTab
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    registerNativeCreativeTab(
        id = "mymod:my_tab",
        registerMode = RegisterMode.RELOADABLE
    ) {
        CreativeModeTab.Builder(CreativeModeTab.Row.TOP, 0)
            .title(Component.literal("My Custom Tab"))
            .icon { ItemStack(Items.DIAMOND) }
            .displayItems { _, items ->
                items.accept(Items.DIAMOND)
                items.accept(Items.EMERALD)
            }
            .build()
    }
}

9. 数据组件类型

类型安全的物品堆叠自定义数据——可以理解为结构化 NBT。

kotlin
import com.mojang.serialization.Codec
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativePersistentDataComponentType
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    registerNativePersistentDataComponentType(
        id = "mymod:custom_data",
        registerMode = RegisterMode.RELOADABLE,
        codec = Codec.STRING
    )
}

Data components let you attach custom data to item stacks — like a built-in NBT but type-safe!

10. 实体类型(基础)

只注册实体类型——无属性、无渲染器、无刷怪蛋。

kotlin
import net.minecraft.core.registries.Registries
import net.minecraft.resources.Identifier
import net.minecraft.resources.ResourceKey
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.Marker
import net.minecraft.world.entity.MobCategory
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeEntityType
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    // A minimal marker-based entity type with no attributes or spawn egg.
    registerNativeEntityType(
        id = "mymod:my_entity",
        registerMode = RegisterMode.RELOADABLE
    ) {
        val key = ResourceKey.create(
            Registries.ENTITY_TYPE,
            Identifier.parse("mymod:my_entity")
        )
        EntityType.Builder.of(::Marker, MobCategory.MISC)
            .sized(0.6f, 1.8f)
            .build(key)
    }
}

11. 实体类型(完整——含属性 + 刷怪蛋)

完整实体注册:包含属性、刷怪蛋和生成配置。

kotlin
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.MobCategory
import net.minecraft.world.entity.SpawnPlacementTypes
import net.minecraft.world.entity.monster.zombie.Zombie
import top.katton.api.ClientPhase
import top.katton.api.ClientScriptEntrypoint
import top.katton.api.ServerPhase
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.registry.registerNativeEntity
import top.katton.registry.RegisterMode

@ServerScriptEntrypoint(ServerPhase.READY)
@ClientScriptEntrypoint(ClientPhase.REGISTRY_SETUP)
fun main() {
    registerNativeEntity(
        id = "mymod:my_mob_full",
        registerMode = RegisterMode.RELOADABLE,
        configure = {
            dimensions(0.6f, 1.95f)
            category = MobCategory.MONSTER
            maxHealth(20.0)
            movementSpeed(0.23)
            attackDamage(3.0)
            followRange(35.0)
            withSpawnEgg()
            spawnPlacement(SpawnPlacementTypes.ON_GROUND)
        }
    ) { p ->
        val key = ResourceKey.create(Registries.ENTITY_TYPE, p.id)
        EntityType.Builder.of(::Zombie, p.category)
            .sized(p.dimensions.width, p.dimensions.height)
            .build(key)
    }
}

TIP

要了解完整的自定义动画实体教程(从 BlockBench 模型到游戏内动画),请看 实体教程