# Kommand Lib Paper/Bukkit サーバー向けのコマンド定義を DSL で記述するためのライブラリです。ルート定義から引数の型、補完、パーミッション伝播までを宣言的に表現でき、手続き的な `CommandExecutor` 実装を大きく簡略化します。 ## 特徴 - Kotlin DSL で `command { literal { argument { ... } } }` のようにネストを表現 - 型付き引数 (`string`, `integer`, `float`, `player`, `selector`, `coordinates` など) と検証ロジックを組み込み - 1 つの定義から実行とタブ補完の両方を生成 - パーミッションや条件をノード単位で宣言し、子ノードへ自動伝播 - `suggests {}` で引数ごとの補完候補を柔軟に制御 - `permits-lib` との連携により、コマンドツリーから Bukkit パーミッションを自動生成し、`compileOnly` 依存として参照可能 ## 依存関係 `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 { ... }` 内で `string("player")` や `int("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("target") val amount = int("amount") sender.sendMessage("Giving $amount to ${target.name}") } } literal("speed") { players("targets") // @a, プレイヤー名, などをまとめて取得 float("value", min = 0.1, max = 5.0) executes { val targets = players("targets") val speed = float("value") targets.forEach { it.walkSpeed = speed.toFloat() / 5.0f } sender.sendMessage("Updated ${targets.size} players") } } literal("setspawn") { coordinates("point") // "~ ~1 ~-2" のような入力を受け付ける executes { val base = (sender as? Player)?.location ?: return@executes val target = location("point", base) plugin.server.worlds.first().setSpawnLocation(target) sender.sendMessage("Spawn set to ${target.x}, ${target.y}, ${target.z}") } } literal("inspect") { selector("entities") executes { val entities = selector("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` に記憶されます。 - `float("speed")` は倍精度を、`player("target")`/`players("targets")`/`selector("entities")` は Minecraft の標準セレクター (`@p`, `@a`, `@s` など) やプレイヤー名を型付きで扱えます。 - `suggests { prefix -> ... }` を指定すると、タブ補完時に任意の候補リストを返せます。 - `coordinates("pos")` は `x y z` をまとめて 1 つの引数として受け取り、`location("pos", player.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" tags("economy") } 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` | Bukkit の `Bukkit.selectEntities` をそのまま利用 | | `coordinates("pos")` | `Coordinates3` | `~` 相対座標を含む 3 軸をまとめて扱う | `Coordinates3` は `coordinates("pos") { ... }` 直後のコンテキストで `coordinates("pos")` もしくは `location("pos", baseLocation)` として取得できます。 ## ビルドとテスト ```bash ./gradlew build ``` ShadowJar タスクが実行され、`build/libs` に出力されます。Paper サーバーに配置して動作確認してください。