minecraft-plugin-dev について
Paper、Spigot、Bukkit、Folia を対象とする Minecraft サーバープラグイン開発スキルです。プロジェクト設定、イベントやコマンド、データ保存、検証を案内します。
主な機能
- 01Gradle プロジェクトとプラグイン記述子の設定
- 02イベントリスナーとコマンドの実装
- 03Paper と Folia のスケジューリング対応
- 04PDC、YAML、Adventure テキストの利用
- 05プラグインレイアウト検証の実行
活用シーン
- 01Paper・Spigot プラグインの作成や保守
- 02Folia 向けタスクスケジューリングの調整
- 03プレイヤーデータ、設定、コマンドの実装
- 04プラグイン JAR と記述子のビルド・確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jahrome907/ minecraft-agent-skills --skill minecraft-plugin-devインストール後、Claude Code で/minecraft-plugin-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/paper-plugin-commands.mdruntime-patterns.mdscripts/validate-plugin-layout.shscripts/vendor/js-yaml.min.cjsLICENSE.js-yaml詳細
Minecraft プラグイン開発スキル
プラットフォームの概要
| プラットフォーム | 基本 API | 備考 |
|---|---|---|
| Paper | Bukkit/Spigot + Paper 拡張機能 | 推奨。非同期チャンク読み込み、Adventure をネイティブサポート |
| Spigot | Bukkit + Spigot 拡張機能 | レガシー。API が少なく、低速 |
| Bukkit | 基本 API のみ | 新しいプラグインでは避けてください |
| Folia | Paper のフォーク | リージョンスレッド方式。専用のスケジューラー API が必要 |
Paper を推奨対象とします。Paper にはすべての Bukkit および Spigot API に加え、 大幅なパフォーマンス向上と追加 API が含まれています。
適用範囲
Use when:対象が、JavaPlugin API を使用するサーバー側の Paper/Bukkit/Spigot プラグインの動作である場合。Do not use when:タスクでクライアント側にインストール可能な MOD やローダー API が必要な場合(minecraft-modding/minecraft-multiloader)。Do not use when:タスクがバニラのデータパックやコマンドのコンテンツのみを扱う場合(minecraft-datapack/minecraft-commands-scripting)。
同梱リファレンス
- スケジューリング、Folia 対応、PDC、Adventure/MiniMessage、YAML 設定、Vault、または Paper 固有の API を扱うタスクでは、
references/runtime-patterns.mdを読んでください。 - Paper 専用の
paper-plugin.ymlプロジェクトや Brigadier コマンド登録では、references/paper-plugin-commands.mdを読んでください。
プロジェクトのセットアップ
以下の例は、現行の Paper 26.2 と Java 25 を対象としています。既存の
1.21.x プラグインでは、プロジェクトを意図的に移行するまでは
1.21.x-R0.1-SNAPSHOT 依存関係、Java 21 ツールチェーン、および対応する
api-version を維持してください。
settings.gradle.kts
rootProject.name = "my-plugin"
build.gradle.kts
plugins {
java
}
group = "com.example"
version = "1.0.0-SNAPSHOT"
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:26.2.build.+")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
tasks {
processResources {
// Substitutes ${version} in plugin.yml with the Gradle project version
filesMatching(listOf("plugin.yml", "paper-plugin.yml")) {
expand("version" to project.version)
}
}
}
Shadow は、実行時ライブラリをバンドルして再配置する必要がある場合にのみ追加してください。
Paper や Vault などのオプションのプラグイン API は compileOnly のままにし、
プラグイン JAR にシェーディングしてはいけません。
gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
Gradle 8.8 は Java 25 では実行できません。現行の Java 25 プロジェクトでは、Gradle 9.1 以降を使用してください。レガシーな 1.21.x プロジェクトでは、ビルドを意図的にアップグレードして検証しない限り、 既存のラッパーと Java 21 ツールチェーンを維持してください。
プロジェクト構成
my-plugin/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle/
│ └── wrapper/
│ └── gradle-wrapper.properties
└── src/main/
├── java/com/example/myplugin/
│ ├── MyPlugin.java ← main class (extends JavaPlugin)
│ ├── listeners/
│ │ └── PlayerListener.java
│ ├── commands/
│ │ └── MyCommand.java
│ └── managers/
│ └── DataManager.java
└── resources/
├── plugin.yml ← Bukkit-compatible descriptor
├── paper-plugin.yml ← active descriptor for a Paper plugin
└── config.yml
コアファイル
plugin.yml(Bukkit 互換のデフォルト)
name: MyPlugin
version: "${version}"
main: com.example.myplugin.MyPlugin
description: An example Paper plugin
author: YourName
website: https://github.com/example/my-plugin
api-version: '26.2'
commands:
myplugin:
description: Main plugin command
usage: /myplugin <subcommand>
permission: myplugin.use
aliases: [mp]
permissions:
myplugin.use:
description: Allows use of /myplugin
default: true
myplugin.admin:
description: Admin access
default: op
api-versionは、プラグインが意図的にサポートする最も古い Paper API に合わせてください。 現行の Paper の例では26.2を使用します。レガシーな1.21および正の値である1.21.<patch>は、古いサーバーでも引き続き有効です。宣言された値より古いサーバーでは、 プラグインの読み込みが拒否されます。
paper-plugin.yml(実験的な Paper 専用形式)
Bukkit 互換のプラグインでは plugin.yml を優先してください。paper-plugin.yml は、
ブートストラップ、ローダー、クラス読み込みの分離など、Paper プラグインの動作が
必要な、JAR が意図的に Paper 専用である場合にのみ使用してください。これだけを
記述ファイルとして使用できますが、単純な置き換えにはなりません。Paper プラグインでは
commands フィールドや getCommand(...) による登録は使用できません。対応する
記述ファイル、メインクラス、および Brigadier のライフサイクル登録については、
references/paper-plugin-commands.md
を読んでください。
1 つの JAR に両方の記述ファイルを含める場合は、共通のメタデータとメインクラスが
一致するようにしてください。Paper 専用のサンプルを、以下の Bukkit 互換の
MyPlugin サンプルと組み合わせないでください。
Bukkit 互換のメインクラス
package com.example.myplugin;
import com.example.myplugin.commands.MyCommand;
import com.example.myplugin.listeners.PlayerListener;
import org.bukkit.plugin.java.JavaPlugin;
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
saveDefaultConfig();
// Register listeners
getServer().getPluginManager().registerEvents(new PlayerListener(), this);
// Register commands
var cmd = getCommand("myplugin");
if (cmd == null) {
throw new IllegalStateException("myplugin command is missing from plugin.yml");
}
var handler = new MyCommand(this);
cmd.setExecutor(handler);
cmd.setTabCompleter(handler);
getLogger().info("MyPlugin enabled!");
}
@Override
public void onDisable() {
getLogger().info("MyPlugin disabled.");
}
}
イベントリスナー
package com.example.myplugin.listeners;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
public void onPlayerJoin(PlayerJoinEvent event) {
event.joinMessage(
Component.text(event.getPlayer().getName() + " joined!", NamedTextColor.GREEN)
);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
event.quitMessage(
Component.text(event.getPlayer().getName() + " left.", NamedTextColor.YELLOW)
);
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
// Modify death message using Adventure components
event.deathMessage(
Component.text("☠ ", NamedTextColor.RED)
.append(Component.text(event.getPlayer().getName(), NamedTextColor.WHITE))
.append(Component.text(" died!", NamedTextColor.RED))
);
}
}
EventPriority の順序
LOWEST → LOW → NORMAL → HIGH → HIGHEST → MONITOR
MONITOR はログ記録にのみ使用してください(結果を変更してはいけません)。Cancellable を実装するイベントでは、
キャンセルされたイベントが必要な場合を除き、ignoreCancelled = true を使用してください。
キャンセル可能なイベント
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (event.getPlayer().hasPermission("myplugin.break.deny")) {
event.setCancelled(true);
event.getPlayer().sendMessage(Component.text("You cannot break blocks!", NamedTextColor.RED));
}
}
コマンド
このセクションは、上記の plugin.yml を使用する方法を対象としています。宣言されたコマンドにより
getCommand("myplugin") を使用できます。Paper 専用の記述ファイルでは、
references/paper-plugin-commands.md の Brigadier
ライフサイクルの例を使用してください。
package com.example.myplugin.commands;
import com.example.myplugin.MyPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
public class MyCommand implements CommandExecutor, TabCompleter {
private final MyPlugin plugin;
public MyCommand(MyPlugin plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("Only players can use this command.", NamedTextColor.RED));
return true;
}
if (!player.hasPermission("myplugin.use")) {
player.sendMessage(Component.text("No permission.", NamedTextColor.RED));
return true;
}
if (args.length == 0) {
player.sendMessage(Component.text("Usage: /myplugin <reload|info>", NamedTextColor.YELLOW));
return true;
}
return switch (args[0].toLowerCase(Locale.ROOT)) {
case "reload" -> {
plugin.reloadConfig();
player.sendMessage(Component.text("Config reloaded.", NamedTextColor.GREEN));
yield true;
}
case "info" -> {
player.sendMessage(Component.text("Version: " + plugin.getDescription().getVersion(), NamedTextColor.AQUA));
yield true;
}
default -> {
player.sendMessage(Component.text("Unknown subcommand.", NamedTextColor.RED));
yield true;
}
};
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (args.length == 1) {
return List.of("reload", "info").stream()
.filter(s -> s.startsWith(args[0].toLowerCase(Locale.ROOT)))
.toList();
}
return List.of();
}
}
スケジューラー
従来型の Paper プラグインでは、BukkitSchedulerを引き続き使用できます。Folia をサポートするとする場合は、プレイヤー、エンティティ、リージョン、グローバル、非同期の処理を、対応する Folia 対応スケジューラー経由で実行してください。1 つのプラグインで Paper と Folia の両方をサポートする場合は、スケジューリングをプロジェクト内の小さなインターフェースの背後に隠してください。
コピーして使える同期、非同期、キャンセル可能、Folia セーフなスケジューラーの例については、references/runtime-patterns.mdを参照してください。
永続データコンテナー(PDC)
PDC は、あらゆるPersistentDataHolder(プレイヤー、エンティティ、アイテム、チャンク)に任意のデータを保存します。データはワールドとともに保存され、再起動後も保持されます。
NamespacedKeyのインスタンスは一度だけ作成し、リリース後はデータ型を変更せず、大規模なデータセットではなく小さなメタデータに PDC を使用してください。大規模なデータやクエリ頻度の高いプラグインの状態には、設定ファイルまたはデータベースを優先してください。
プレイヤー、アイテム、チャンク、ワールドの PDC の例については、references/runtime-patterns.mdを参照してください。
Adventure テキストコンポーネント
Paper はすべてのテキストにAdventureをネイティブに使用します。レガシーなチャットカラーは使用しません。コードで管理するメッセージにはComponentビルダーを、設定で管理するメッセージには MiniMessage を使用してください。互換性のために対象プロジェクトがすでに依存している場合を除き、レガシーなChatColorは避けてください。
シンプルなメッセージ、ホバー/クリックイベント、MiniMessage の解析、タイトル、アクションバーの例については、references/runtime-patterns.mdを参照してください。
設定(YAML)
src/main/resources/config.yml
# Default config
settings:
max-players: 20
welcome-message: "<green>Welcome to the server!"
cooldown-seconds: 30
database:
host: localhost
port: 3306
name: myplugin_db
設定値へのアクセス
onEnable()でsaveDefaultConfig()を呼び出し、値を読み取る際は明示的なデフォルト値を指定し、長時間実行するタスクを開始する前に設定の構造を検証してください。
カスタム設定ファイル
ユーザー設定と変更可能なプラグインデータを分離するメリットが、ファイル処理の増加に見合う場合にのみ、カスタム YAML ファイルを使用してください。頻繁に実行されるイベント処理で、ブロッキングを伴うディスク書き込みを行わないでください。
設定の読み書きとカスタム YAML の例については、references/runtime-patterns.mdを参照してください。
Vault 連携(経済/権限)
Vault をcompileOnlyとして宣言し、プラグインのメタデータでソフト依存に指定してください。サービスプロバイダーを利用できない場合は、経済機能を適切に無効化してください。Vault 自体が存在するからといって、Vault 互換の経済プラグインがインストールされていると決めつけないでください。
Vault サポートが必要な場合は、JitPack リポジトリとcompileOnly("com.github.MilkBowl:VaultAPI:1.7")を Gradle ビルドに追加してください。
最小限の経済機能の設定と課金の例については、references/runtime-patterns.mdを参照してください。
Paper 固有の API
メインスレッドのブロッキングをなくしたり、Adventure ネイティブの動作を簡素化したりできる場合は、Paper API を使用してください。オプションのプラグイン連携は、存在確認とメタデータのソフト依存を通じて管理してください。
非同期チャンク読み込み、カスタムアイテムメタ、プロフィール検索、保護プラグイン連携の例については、references/runtime-patterns.mdを参照してください。
よくあるタスクのチェックリスト
新しいイベントリスナーの作成
-
Listenerを実装するクラスを作成する - メソッドに
@EventHandlerを付ける -
onEnable()でgetServer().getPluginManager().registerEvents(listener, plugin)を呼び出す - キャンセル可能なイベントでは、キャンセル済みイベントが必要な場合を除き、
ignoreCancelled = trueを追加する
新しいコマンドの追加
- Bukkit 互換プラグインでは、
plugin.ymlでコマンドを定義し、CommandExecutorを作成して、getCommand("name")で登録する - Paper 専用プラグインでは、
LifecycleEvents.COMMANDSを通じてコマンドを登録する。commandsフィールドは追加しない
プラグインデータの保存
- 単純な値の場合:
getConfig()/saveConfig()を使ってconfig.ymlに保存する - エンティティごとのデータの場合:
NamespacedKeyを使って PDC に保存する - 大規模なデータセットの場合:非同期スケジューラーとファイル I/O、またはデータベースを使う
繰り返しタスクのスケジューリング
- タスクにメインスレッドが必要か(
runTaskTimerを使う)、I/O 処理か(runTaskTimerAsynchronouslyを使う)を判断する -
BukkitTaskの参照を保持し、onDisable()でキャンセルできるようにする -
onDisable()ですべてのタスクをキャンセルするか、getServer().getScheduler().cancelTasks(plugin)を使う
ビルド、検証、実行
- プラグイン JAR をビルドします:
./gradlew build # Output: build/libs/my-plugin-1.0.0-SNAPSHOT.jar - 同梱のバリデーターを実行し、設定とレイアウトのエラーを検出します:
バリデーターの実行には Node が必要です。ディスクリプターのチェックに使用する YAML パーサーが同梱されています。./scripts/validate-plugin-layout.sh --root /path/to/plugin-project # Strict mode treats warnings as failures: ./scripts/validate-plugin-layout.sh --root /path/to/plugin-project --strict - 報告されたエラーを修正し、エラーがなくなるまで再実行します。
- デプロイします:ビルドした JAR を
server/plugins/にコピーし、Paper サーバーを再起動します。 実際のプロジェクトですでにxyz.jpenilla.run-paperのような Paper 開発サーバープラグインを適用している場合は、./gradlew runServerがあると想定せず、そのプロジェクトでドキュメント化されている開発タスクを使用してください。
バリデーターは次の項目をチェックします:
- 有効な
plugin.ymlまたはpaper-plugin.ymlの必須キー(name、version、main、api-version)と、リポジトリでサポートされている現行の26.<release>または旧形式の1.21/正の値の1.21.<patch>。ドキュメントの例より新しいバージョンには警告を表示します - 両方のディスクリプターが存在する場合のメタデータの整合性。有効なものとして
paper-plugin.ymlが選択されます - 有効なメインクラスのパスが存在し、
JavaPluginを継承していること Bukkit.reload()やサーバーのリロードコマンドの実行など、実際のサーバーでの/reloadのアンチパターン
参考資料
- Paper API Javadoc:https://jd.papermc.io/paper/
- Paper プラグインディスクリプター:https://docs.papermc.io/paper/dev/plugin-yml/
- Paper プラグイン:https://docs.papermc.io/paper/dev/getting-started/paper-plugins/
- Paper Brigadier の登録:https://docs.papermc.io/paper/dev/command-api/basics/registration/
- Gradle の Java 互換性:https://docs.gradle.org/current/userguide/compatibility.html
- Adventure(テキスト API):https://docs.advntr.dev/
- MiniMessage 形式:https://docs.advntr.dev/minimessage/format.html
- Vault API:https://github.com/MilkBowl/VaultAPI
- Bukkit API Javadoc:https://javadoc.io/doc/org.bukkit/bukkit/
- run-task Gradle プラグイン:https://github.com/jpenilla/run-task