ガイド

Kotlin と Java

大規模な Mod 向けです。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 のクラスを呼び出して回避しないでください。ゲームが変わっても動き続けるという保証を Mod が失ってしまいます。

プロジェクトを作る

ターミナル
crafting new rubies --kind kotlin
cd rubies && gradle build

API はまだ公開の Maven リポジトリにありません。Crafting のソースからローカルの Maven にインストールし、compileOnly として宣言してください。実行時には Crafting が Mod に提供します。

ターミナル
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 です。

構成

テーマごとのパッケージ、各部分をつなぐだけのエントリークラス、そして部分ごとに 1 つのファイル。理由は Mod の整理 ガイドで説明しています。これが完全なサンプル Mod です。

構成テキスト
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。クライアントの Mod は、マルチプレイ用サーバーでは何も言わずに無視されます。
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:すべての Mod が読み込まれています。Crafting.runtime.isModLoaded("other_mod") で別の Mod があるかどうかがわかります。
  4. onShutdown:終了時。

サーバープラグイン

プラグインは "kind": "plugin" と "side": "server" を持つ CraftingMod です。crafting new chatguard --kind plugin で、エントリークラス、events/ChatFilter.kt、commands/HelloCommand.kt が作成されます。ブロックやアイテムを追加しないので、プラグインしかないサーバーには Mod なしの 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 を置き換えると自動でこれが行われます。

Mod プラグイン
フォルダー mods/ plugins/
動作する側 クライアントとサーバーのどちらか、または両方 サーバーのみ
新しいブロックとアイテム 可 不可
再起動なしの再読み込み 不可 可
Mod なしのクライアントの参加 コンテンツを追加する場合は不可 可

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(minecraft:fall のような id)
world.setBlockState(x, y, z, "…[open=true]") world.setBlock(x, y, z, type, mapOf("open" to true))

ルール

  • net.minecraft からは何もインポートしないでください。それが、ゲームが変わっても Mod が動き続けられる理由です。
  • コンテンツは onLoad でのみ登録します。
  • runAsync のタスクはワールドに触れません。ゲームのスレッドには runSync で戻ります。