# Kommand Lib Paper/Bukkit サーバー向けのコマンド定義を DSL で記述するためのライブラリです。ルート定義から引数の型、補完、パーミッション伝播までを宣言的に表現でき、手続き的な `CommandExecutor` 実装を大きく簡略化します。 ## 特徴 - Kotlin DSL で `command { literal { argument { ... } } }` のようにネストを表現 - 型付き引数 (`string`, `integer`, `float`, `player`, `selector`, `coordinates` など) と検証ロジックを組み込み - 1 つの定義から実行とタブ補完の両方を生成 - パーミッションや条件をノード単位で宣言し、子ノードへ自動伝播 - `suggests {}` で引数ごとの補完候補を柔軟に制御 - Brigadier (Paper 1.21 Lifecycle API) 対応により、クライアント側で ` ` のような構文ヒントや、数値範囲の検証エラー(赤文字)が表示されます - `permits-lib` との連携により、コマンドツリーから Bukkit パーミッションを自動生成し、`compileOnly` 依存として参照可能 ## バージョン情報 **現在のバージョン**: 1.1 (Brigadier ネイティブ対応) ### 🔄 旧バージョンからの移行 旧バージョン (Brigadier 対応前) から移行する場合は、[マイグレーションガイド](./MIGRATION_GUIDE.md) を参照してください。 **主な変更点**: - `coordinates()` の返り値が `Coordinates3` から `io.papermc.paper.math.Position` に変更 - `position.toLocation(world)` で `Location` に変換する方式に変更 - Player/Entity セレクターの内部処理が改善され、より安定した動作を実現 ## 依存関係 `build.gradle.kts` では Paper API と Kotlin 標準ライブラリのみを `compileOnly` に追加しています。Paper 1.21.10 対応の API を利用しています。 ```kotlin plugins { kotlin("jvm") version "2.2.21" id("de.eldoria.plugin-yml.paper") version "0.8.0" id("com.gradleup.shadow") version "9.2.2" } dependencies { compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") compileOnly("org.jetbrains.kotlin:kotlin-stdlib") // ../permits-lib を includeBuild しているので module 参照でOK compileOnly("net.hareworks.hcu:permits-lib:1.0") } ``` ## 使い方 1. プラグインの `onEnable` などで `kommand(plugin) { ... }` DSL を呼び出します。 2. `command("root", "alias") { ... }` でコマンドを宣言し、`literal` や `string`/`integer` 引数を追加します。 3. `executes { ... }` は必ず対象のノード (`literal`, `player`, `integer` など) の中にネストします。これにより、そのノードまでに宣言した引数を `argument("player")` や `argument("amount")` のように取得できます。 ### サンプル: 経済コマンド ```kotlin class EconomyPlugin : JavaPlugin() { private lateinit var commands: KommandLib override fun onEnable() { commands = kommand(this) { command("eco", "economy") { description = "Economy management" permission = "example.eco" literal("give") { player("target") { // プレイヤー名 or セレクター (@p 等) integer("amount", min = 1) { executes { val target: Player = argument("target") val amount = argument("amount") sender.sendMessage("Giving $amount to ${target.name}") } } } } literal("speed") { players("targets") { // @a, プレイヤー名, などをまとめて取得 float("value", min = 0.1, max = 5.0) { executes { val targets: List = argument("targets") val speed = argument("value") targets.forEach { it.walkSpeed = speed.toFloat() / 5.0f } sender.sendMessage("Updated ${targets.size} players") } } } } literal("setspawn") { coordinates("point") { // "~ ~1 ~-2" のような入力を受け付ける executes { val player = sender as? Player ?: return@executes val position = argument("point") val location = position.toLocation(player.world) player.world.setSpawnLocation(location) sender.sendMessage("Spawn set to ${location.x}, ${location.y}, ${location.z}") } } } literal("inspect") { selector("entities") { executes { val entities: List = argument("entities") sender.sendMessage("Selector resolved ${entities.size} entities") } } } } } } override fun onDisable() { commands.unregister() } } ``` ### DSL 構文のポイント - `literal("sub") { ... }` は固定語句を表すノードです。`requires("permission.node")` でその枝のみにパーミッションを設定できます。 - `string("name")` や `integer("value", min = 0)` は値をパースし、成功すると `KommandContext` に記憶されます。取得時は `argument("name")` や `argument("value")` を呼び出してください。 - `float("speed")` や `player("target")`/`players("targets")`/`selector("entities")` は Minecraft の標準セレクター (`@p`, `@a`, `@s` など) やプレイヤー名を型付きで扱えます。実行時は `argument("speed")`、`argument("target")`、`argument>("targets")` のように取得できます。 - `suggests { prefix -> ... }` を指定すると、タブ補完時に任意の候補リストを返せます。 - `coordinates("pos")` は `x y z` をまとめて 1 つの引数として受け取り、`argument("pos")` で取得できます。`position.toLocation(world)` で `Location` に変換できます (`~` を使用した相対座標に対応)。 - `command` や各ノードの `condition { sender -> ... }` で実行条件 (例: コンソール禁止) を追加できます。 - ルートレベルで `executes { ... }` を指定すると、引数なしで `/eco` を実行した場合に呼び出されます。 ## 自動パーミッション生成 (`permits-lib`) `permits-lib` を `compileOnly` で参照している場合、DSL から Bukkit パーミッションツリーを自動生成できます。 ```kotlin commands = kommand(this) { permissions { namespace = "hareworks" rootSegment = "command" defaultDescription { ctx -> "Allows /${ctx.commandName} (${ctx.path.joinToString(" ")})" } } command("eco") { permission { description = "Allows /eco" } literal("give") { permission { description = "Allows /eco give" } } } } ``` - `permissions { ... }` で名前空間やルートセグメント (`rootSegment = "command"`) を定義すると、`hareworks.command.eco`, `hareworks.command.eco.give` のような ID が自動生成され、`permits-lib` の `MutationSession` を使って Bukkit に適用されます。 - `literal` や `command` ノードはデフォルトで Bukkit パーミッションとして登録されます。`string` や `integer` などの引数ノードは、`requires("permission.id")` もしくは `permission { id = ... }` を記述した場合のみ登録され、それ以外は構造上のノードに留まります。これにより `/money give ` では `...money.give` だけ付与すれば実行できます。 - 各ブロック内の `permission { ... }` で説明文・デフォルト値 (`PermissionDefault.TRUE/FALSE/OP/NOT_OP`)・ワイルドカード・パスを細かく制御できます。`requires(...)` を使うと DSL での検証と permits 側の登録が同じ ID になります。 - 引数ノードを明示的に登録したい場合は `permission { id = "example.money.give.amount" }` や `requires("example.money.give.amount")` を設定してください。逆にリテラルでも不要なら `skipPermission()` で除外できます。 - `requires("custom.id")` を指定した場合も、同じ ID が DSL の実行と `permits-lib` への登録の両方で利用されます (名前空間外の ID は登録対象外になります)。 - `KommandLib` のライフサイクルに合わせて `MutationSession` が適用/解除されるため、プラグインの有効化・無効化に伴い Bukkit のパーミッションリストも最新状態に保たれます。 ## 組み込み引数の一覧 | DSL | 返り値 | 補足 | | --- | --- | --- | | `string("name")` | `String` | 任意のトークン。`suggests {}` で補完可 | | `integer("value", min, max)` | `Int` | 範囲チェック付き | | `float("speed", min, max)` | `Double` | 小数/指数表記に対応 | | `player("target", allowSelectors = true)` | `Player` | `@p` などのセレクターまたはプレイヤー名を 1 人に解決 | | `players("targets")` | `List` | `@a`/`@r` など複数指定、プレイヤー名入力も可 | | `selector("entities")` | `List` | エンティティセレクター (`@e` など) | | `coordinates("pos")` | `io.papermc.paper.math.Position` | `~` 相対座標を含む 3 軸をまとめて扱う | `Position` は `coordinates("pos") { ... }` 直後のコンテキストで `argument("pos")` として取得でき、`position.toLocation(world)` で `Location` に変換できます。 ## クライアント側構文ヒント (Brigadier) Paper 1.21 以降の環境では、`LifecycleEventManager` を通じてコマンドが登録されるため、クライアントにコマンドの構造が送信されます。これにより以下のメリットがあります: - **構文の可視化**: 入力中に ` ` のような引数名が表示されます。 - **クライアント側検証**: `integer("val", min=1, max=10)` などの範囲指定がクライアント側でも判定され、範囲外の値を入力すると赤字になります。 - **互換性**: 内部的には `Brigadier` のノードに変換されますが、実際のコマンド実行は `kommand-lib` の既存ロジック(`KommandContext`)を使用するため、古いコードの修正は不要です。 ## ビルドとテスト ```bash ./gradlew build ``` ShadowJar タスクが実行され、`build/libs` に出力されます。Paper サーバーに配置して動作確認してください。 ## ドキュメント - **[マイグレーションガイド](./MIGRATION_GUIDE.md)** - 旧バージョンからの移行方法 ## ライセンス このプロジェクトは MIT ライセンスの下で公開されています。