指南

Kotlin 与 Java

面向大型模组。与 CraftScript 相同的 API,加上静态类型、完整的 IDE 和任意 Java 库,另有少数目前只在这里提供的部分。本指南使用 Kotlin;Java 的用法相同,放在最后展示。

能做什么

领域 API 中 CraftScript 中
物品、方块、配方 ContentBuilder:item、block、shaped、shapeless、smelting 支持
生物 ContentBuilder.creature:身体、纹理、属性、Behaviour、TargetRule、战利品、自然生成、驯服、繁殖、骑乘、自定义行为 支持
声音与粒子 ContentBuilder.sound、ContentBuilder.particle 支持
世界生成 ContentBuilder:ore、tree、patch、structure(池、plan/build)、biome、dimension、feature、terrain、biomes、carver、tag。见世界生成和用代码生成 除 feature、tag、salt、aboveBottom 外全部支持
界面 Form 和 player.showForm;server.bossBar;player.setSidebar 支持
事件 Crafting.events.on<E> { },支持优先级和取消 支持
效果、属性、装备 addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot Yes
按键 ContentBuilder.key, PlayerKeyEvent Yes
命令 Crafting.commands.command(…):子命令、类型化参数、权限、别名 支持
世界与玩家 World、Block、Entity、Player、Inventory、Server 支持
任务 Crafting.scheduler:runLater、runRepeating、runAsync、runSync 不支持后台任务
文件设置 TOML 格式的类型化 Config,可与客户端同步 支持(config)
网络 Crafting.network.channel(…),支持自定义编解码器 支持(listen、send)
存档数据 player.data["rubies.visits"] = n、data.getInt(…);也可用于实体、方块和世界 支持
Java 库 任意 不支持

API 尚未提供的功能在任何语言中都不存在。不要通过调用 Minecraft 的类来绕过:那样模组就失去了在游戏变化后继续运行的保证。

创建项目

终端
crafting new rubies --kind kotlin
cd rubies && gradle build

API 还没有发布到公共 Maven 仓库。请从 Crafting 源码把它安装到本地 Maven,并声明为 compileOnly:运行时由 Crafting 提供给模组。

终端
cd crafting && ./gradlew :crafting-api:publishToMavenLocal
build.gradle.ktsKotlin
plugins { kotlin("jvm") version "2.4.21" }

repositories { mavenCentral(); mavenLocal() }

dependencies {
    compileOnly("dev.crafting:crafting-api:0.1.0")
}

kotlin { jvmToolchain(25) } // Minecraft 26.x 运行在 Java 25 上

crafting new 会生成这个文件,以及一个已按职责拆分好的项目:只负责连接各部分的 <Name>Mod.kt、content/ModContent.kt、events/Welcome.kt 和 commands/GemCommand.kt。

结构

按主题分包,入口类只负责连接各部分,每个部分一个文件。组织模组指南解释了原因;下面是完整的示例模组:

结构文本
rubies/
├── build.gradle.kts
├── settings.gradle.kts
└── src/main/
    ├── kotlin/com/example/rubies/
    │   ├── RubiesMod.kt         entry point: only wires the parts
    │   ├── RubiesConfig.kt      player settings
    │   ├── content/
    │   │   ├── Items.kt
    │   │   ├── Blocks.kt
    │   │   ├── Creatures.kt
    │   │   ├── Sounds.kt
    │   │   ├── Particles.kt
    │   │   └── Recipes.kt
    │   ├── storage/
    │   │   └── Visits.kt        saved data: the keys, in one place
    │   ├── events/
    │   │   ├── MiningEvents.kt
    │   │   ├── CombatEvents.kt
    │   │   └── WelcomeEvents.kt
    │   ├── ui/
    │   │   └── ShopForm.kt       forms: what the player sees
    │   └── commands/
    │       ├── RubyCommand.kt
    │       └── ShopCommand.kt
    └── resources/
        ├── crafting.mod.json
        └── assets/rubies/
            ├── sounds/ruby/chime.ogg
            └── textures/
                ├── item/ruby.png
                ├── entity/ruby_golem.png
                └── particle/ruby_spark_0.png, ruby_spark_1.png

src/main/kotlin/com/example/rubies/

入口类本身不做任何事:它创建共享的部分并调用每个部分。

src/main/kotlin/com/example/rubies/RubiesMod.ktKotlin
package com.example.rubies

class RubiesMod : CraftingMod {
    override fun onLoad(ctx: ModContext) {
        val config = Crafting.configs.load(RubiesConfig())
        val content = ContentBuilder(ctx.metadata.id, Crafting.registries)

        Items.register(content)
        Blocks.register(content)
        Creatures.register(content)
        Sounds.register(content)
        Particles.register(content)
        Recipes.register(content)

        MiningEvents.register(config)
        CombatEvents.register(config)
        WelcomeEvents.register()
        RubyCommand.register()
        ShopCommand.register()
    }
}

清单文件

src/main/resources/crafting.mod.jsonJSON
{
  "id": "rubies",
  "name": "Rubies",
  "version": "1.0.0",
  "kind": "mod",
  "side": "both",
  "entrypoints": ["com.example.rubies.RubiesMod"],
  "depends": { "crafting": ">=0.1.0" }
}
字段 含义
id 2–64 个字符:小写字母、数字、_ 或 -,以字母开头。它也是你的物品和方块的命名空间。
kind mod 或 plugin(仅服务端,可重载;见下文)。
side both、client 或 server。客户端模组在专用服务器上会被静默忽略。
entrypoints 实现 CraftingMod 的类。
depends、optional、incompatible id → 版本范围:>=1.2 <2、^1.4、~1.4.2、1.20.x、[1.0,2.0)。如果出现问题,Crafting 会一次性报告所有问题并给出建议。
loadAfter、loadBefore 不建立硬依赖的加载顺序。

生命周期

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad:注册内容、事件和命令。
  2. Crafting 在游戏中创建方块和物品。从这时起,注册内容会抛出 RegistryFrozenException。
  3. onReady:所有模组都已加载;Crafting.runtime.isModLoaded("other_mod") 可以告诉你另一个模组是否存在。
  4. onShutdown:退出时调用。

服务器插件

插件是带有 "kind": "plugin" 和 "side": "server" 的 CraftingMod;crafting new chatguard --kind plugin 会创建入口类、events/ChatFilter.kt 和 commands/HelloCommand.kt。插件不添加方块或物品,所以使用未安装模组的 Minecraft 的玩家可以加入只有插件的服务器。启用、禁用和重载插件都无需重启:启用时运行 onLoad 和 onReady,禁用或重载时运行 onShutdown。

终端
crafting new chatguard --kind plugin
src/main/kotlin/com/example/chatguard/ChatGuardPlugin.ktKotlin
package com.example.chatguard

class ChatGuardPlugin : CraftingMod {
    override fun onLoad(ctx: ModContext) {
        ChatFilter.register()
        Crafting.commands.command("guard") {
            permission = "chatguard.admin"
            executes { it.reply("ChatGuard 已启用") }
        }
    }

    override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard 已禁用")
}
src/main/kotlin/com/example/chatguard/events/ChatFilter.ktKotlin
package com.example.chatguard.events

object ChatFilter {
    private val blocked = listOf("spam", "scam")

    fun register() {
        Crafting.events.on<PlayerChatEvent> { e ->
            if (blocked.any { it in e.message.lowercase() }) {
                e.cancel()
                e.player.send("&c请友善发言")
            }
        }
    }
}

插件注册的一切都与它绑定。/crafting reload chatguard 会调用 onShutdown,移除它的监听器、命令和任务,重新读取 jar,再调用 onLoad。在开发模式下,替换 jar 时会自动完成这一过程。

模组 插件
文件夹 mods/ plugins/
运行端 客户端和/或服务端 仅服务端
新方块和新物品 可以 不可以
无需重启即可重载 不可以 可以
未安装模组的客户端可加入 如果添加了内容,则不可以 可以

Java

同样的 API,同样的结构。在 Java 中,入口是 Crafting.INSTANCE,带默认值的参数都要显式传入,监听器返回 Unit.INSTANCE:

src/main/java/com/example/rubies/RubiesMod.javaJava
package com.example.rubies;

public final class RubiesMod implements CraftingMod {
    @Override
    public void onLoad(ModContext ctx) {
        ContentBuilder content = new ContentBuilder(ctx.getMetadata().getId(), Crafting.INSTANCE.getRegistries());
        Items.register(content);
        WelcomeEvents.register();
    }
}
src/main/java/com/example/rubies/events/WelcomeEvents.javaJava
package com.example.rubies.events;

public final class WelcomeEvents {
    public static void register() {
        Crafting.INSTANCE.getEvents().subscribe(PlayerJoinEvent.class, EventPriority.NORMAL, false, null, e -> {
            e.getPlayer().send("&aWelcome, " + e.getPlayer().getName() + "!");
            return Unit.INSTANCE;
        });
    }
}

旧写法

它们仍然可用,但已标记为弃用,并有替代方案:

以前 现在
带 onEnable/onDisable 的 class X : CraftingPlugin() 带 "kind": "plugin" 的 CraftingMod
带 @Subscribe 的方法和 events.register(obj) events.on<E> { }(Java:events.subscribe(E.class, …))
类上的 @Mod("id") 清单文件 entrypoints 中的类
EntityDamageEvent.source damageType(一个 id,例如 minecraft:fall)
world.setBlockState(x, y, z, "…[open=true]") world.setBlock(x, y, z, type, mapOf("open" to true))

规则

  • 不要从 net.minecraft 导入任何东西。正因如此,模组才能在游戏变化后继续运行。
  • 只在 onLoad 中注册内容。
  • runAsync 任务不要操作世界;用 runSync 回到游戏线程。