kommand-lib/README.md

171 lines
9.0 KiB
Markdown

# 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 { ... }` は必ず対象のノード (`literal`, `player`, `integer` など) の中にネストします。これにより、そのノードまでに宣言した引数を `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"
}
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 <player> <amount>` では `...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<Player>` | `@a`/`@r` など複数指定、プレイヤー名入力も可 |
| `selector("entities")` | `List<Entity>` | Bukkit の `Bukkit.selectEntities` をそのまま利用 |
| `coordinates("pos")` | `Coordinates3` | `~` 相対座標を含む 3 軸をまとめて扱う |
`Coordinates3``coordinates("pos") { ... }` 直後のコンテキストで `coordinates("pos")` もしくは `location("pos", baseLocation)` として取得できます。
## ビルドとテスト
```bash
./gradlew build
```
ShadowJar タスクが実行され、`build/libs` に出力されます。Paper サーバーに配置して動作確認してください。