(→plugin.ymlの作成) |
(→plugin.ymlの作成) |
||
255行目: | 255行目: | ||
''plugin.yml'' に、下記の3行を書いてください。 | ''plugin.yml'' に、下記の3行を書いてください。 | ||
− | < | + | |
+ | <source lang="yaml"> | ||
name: (あなたのプラグイン名) | name: (あなたのプラグイン名) | ||
main: (作成したパッケージ名).(作成したメインクラス) | main: (作成したパッケージ名).(作成したメインクラス) | ||
version: (あなたのプラグインのバージョン) | version: (あなたのプラグインのバージョン) | ||
− | </ | + | </source> |
このチュートリアルでは、次のように作成します。 | このチュートリアルでは、次のように作成します。 | ||
− | < | + | <source lang="yaml"> |
name: TutorialPlugin | name: TutorialPlugin | ||
main: my.test.plugin.tutorialplugin.TutorialPlugin | main: my.test.plugin.tutorialplugin.TutorialPlugin | ||
version: 0.0.1 | version: 0.0.1 | ||
− | </ | + | </source> |
{{note}} メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。 | {{note}} メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。 |
2014年3月25日 (火) 03:09時点における版
本ページの内容は、Bukkit WikiのPlugin Tutorialを和訳した物となります。(一部は省略しています)
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。
目次
- 1 始めに
- 2 Javaの習得
- 3 Javaの開発環境
- 4 Plugin用プロジェクトを始めるために
- 5 onEnable()メソッドとonDisable()メソッド
- 6 イベントとリスナ
- 7 コマンド
- 8 堅牢なonCommandの記述
- 9 プラグインのConfiguration/Settings
- 10 権限
- 11 スケジューリングタスクとバックグラウンドタスク
- 12 ブロックの操作
- 13 プレイヤーインベントリの操作
- 14 アイテムの操作
- 15 Matadata
- 16 データベース
- 17 プラグインの配布
- 18 ヒントとノウハウ
- 19 リクエストに応じて書かれた記事
- 20 プラグインのサンプル兼雛形
始めに
- 重要: 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
- 重要: 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
- 重要: 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。当ページの内容を受けて原著者へ何らかのアクションを行う場合、その点を考慮して必ず原文にも目を通して下さい。
このチュートリアルは、Bukkitプラグインを開発するための基本的なノウハウを網羅しております。
Javaを習得する、IDEで開発環境を構築する、などの非常に基本的な部分から説明が始まっていますが、既にご理解頂いている場合は読み飛ばしていただいて構いません。
Javaの習得
- Note: 当記事の一部は原文の訳ではありません、日本語読者向けに独自の内容を記述しています。
このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。
Javaの基礎知識が少ない方は、下記の情報に触れてください。
- Oracleの記事 (現在のJavaの公式サイト)
- とほほのJava入門 - 基礎をみっちり解説している
- Javaのオブジェクト指向入門 - 継承や実装等のオブジェクト指向用語が分からない人向け
Javaがどうしても理解できない場合は、この他にも様々な資料があるので探して触れてください。
Javaの開発環境
- Note: 当記事の一部は原文の訳ではありません、日本語読者向けに独自の内容を記述しています。
プラグインを開発する(またはJavaを学ぶ)前に、開発環境(IDE (Integrated Development Environment))をインストールして下さい。
開発環境は、プラグインのコンパイルとデバッグを行うために使います。
Javaの主要なIDEには、Eclipse, Netbeans, IntelliJ IDEA の3つがあります。
EclipseはBukkit開発者の中では最も人気があり、IntelliJの方は業界内で広く利用されています。
あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。
Eclipseのお勧めのバージョンの配布元は、日本語 Eclipse / Pleiades All in One 日本語ディストリビューションです。
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。
Pleiadesを利用する場合、ビルドツールMavenが実行できるプラグイン「m2e」が同梱されているものを選択してください。
(Eclipseを既に利用している場合でも、m2eを後から追加インストールすることは可能です。)
以下、m2eが既に導入されている前提で説明します。
Plugin用プロジェクトを始めるために
プロジェクトの作成
始めるために、新しいワークスペースを作成する必要があります。
Eclipseを起動し、ファイル>新規>プロジェクト... と選択して下さい。
新規プロジェクトのダイアログが開きます。Mavenフォルダを開いて、Mavenプロジェクト を選択し、次へ を押してください。
次のダイアログで、「シンプルなプロジェクトの作成」にチェックを入れて、次へ を押してください。
次のダイアログで、作成するプラグインの「グループID」と「アーティファクトID」を入力します。
(アーティファクトIDとは、これから作ろうとするプラグインの名前と同義と思っていただいて構いません。)
グループIDは下記の方法で命名して下さい:
- ドメインを持っているなら、そのドメインの逆引き名にしましょう。
- ドメイン名がi-am-a-bukkit-developer.comの場合、パッケージ名はcom.i_am_a_bukkit_developerとなります。
- ドメイン名を持っていない場合、下記の選択肢があります。お勧め順です。
- Option 1 githubやsourceforgeのようなソース管理サイトにアカウント登録します。
- githubの場合: こちらから作成したユーザページのURLを元に、com.github.<username>と命名します。
- Option 2 emailアドレスを元に命名します。(<username>@gmail.comであればcom.gmail.<username>とします)
- Option 3 重複しなさそうなパッケージ名を指定します(他者の開発物と重複する可能性が高いので非推奨)
下記の名前で始まるような、他者の開発物と重複するパッケージで命名すべきではありません:
- org.bukkit
- net.bukkit
- com.bukkit
- net.minecraft
ベースとなるグループIDを決めたら、次にプラグイン名を決めましょう。ここでは例として、TutorialPlugin と名付けます。
ダイアログには次のように入力し、完了 を押してください。
Eclipseの画面に戻ると、左側に TutorialPlugin プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?
Bukkit API の参照設定
次に、左側に作成された TutorialPlugin プロジェクトの中にある、「pom.xml」というファイルをダブルクリックしてください。そして、画面右下のあたりにある「pom.xml」というタブをクリックしてください。
まず、Java実行環境(JRE)の参照設定をします。
一番最後の行に </project> というタグがありますが、その1行上に、次の内容を挿入してください。
(なお、これは Java 7 を参照してビルドするための設定です。もし Java 6 を参照してビルドしたい場合は、sourceタグとtargetタグに書かれている 1.7 のところを 1.6 に変更してください。)
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
次に、BukkitリポジトリのURL設定を追加します。
下記の内容を追記してください。
<repositories>
<repository>
<id>bukkit-repo</id>
<url>http://repo.bukkit.org/content/groups/public/</url>
</repository>
</repositories>
最後に、Bukkit API の参照設定を追加します。
下記の内容を追記してください。
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.6.4-R2.0</version>
</dependency>
</dependencies>
この設定では、Bukkit API 1.6.4-R2.0 が参照されます。
別のバージョンを参照したい場合は、versionタグの中を変更してください。
設定が可能なバージョン番号の一覧は、こちら を参照してください。
ここまで編集をすると、pom.xmlは次のようになっているはずです。確認してみてください。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>my.test.plugin</groupId>
<artifactId>TutorialPlugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>bukkit-repo</id>
<url>http://repo.bukkit.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.6.4-R2.0</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
pom.xmlの編集が完了したら、Ctrl + S でファイルを保存してください。
次に、プロジェクトを右クリックして、Maven>プロジェクトの更新... と選択してください。
Mavenプロジェクトの更新ダイアログが開きます。そのまま OK を押してください。
Eclipseが、Bukkit API のダウンロードを開始します。
しばらく待つと、プロジェクトが更新され、「Maven依存関係」というところにダウンロードされたBukkit APIが表示されます。
また、JREシステム・ライブラリ のところも、指定したJavaビルドバージョンが反映されていることを確認して下さい。
パッケージの作成
次に、作成したプラグインのプロジェクトに、パッケージを追加します。
「src/main/java」のところを右クリックして、新規>パッケージ と選択してください。
パッケージ名は、先ほど設定したグループIDに、プラグイン名を小文字に変換した名前を後ろに付けたものが望ましいです。
ここでは例として、パッケージIDに my.test.plugin、プラグイン名に TutorialPlugin を使っているので、my.test.plugin.tutorialplugin と設定します。
メインクラスの作成
次に、プラグインのメインクラスを作成します。
メインクラスは、JavaPlugin を継承する必要があります。
(逆に、メインクラス以外のクラスは、直接的にも間接的にも、JavaPlugin を継承しないようにしてください。CraftBukkit 1.7.2-R0.3 以降では、プラグインが正しく動作しなくなります。)
メインクラスは、プラグイン名と同じ名前にすることが望ましいです。
先ほど作成したパッケージを右クリックして、新規>クラス と選択してください。
名前の欄にクラス名を入力してください。
スーパークラスの欄に「org.bukkit.plugin.java.javaPlugin」と入力してください。
(参照... ボタンを押して、開いたダイアログに「JavaPlugin」と入力して該当クラスを検索して選択しても構いません。)
完了 を押して、新規クラスが作成されたことを確認して下さい。ソースコードは次のようになっているはずです。
package my.test.plugin.tutorialplugin; import org.bukkit.plugin.java.JavaPlugin; public class TutorialPlugin extends JavaPlugin { }
plugin.ymlの作成
メインクラスが作成できたら、次は plugin.yml ファイルを作成します。
plugin.yml ファイルは、プラグインとしてBukkitに読み込みされるときに、プラグインの設定を記述しておくファイルです。
必須のファイルですから、必ず作成してください。
プロジェクトの src/main/resources を右クリックして、新規>ファイル を選択してください。
開いたダイアログの ファイル名 の欄で、「plugin.yml」と入力し、完了を押してください。
src/main/resources のところに、plugin.yml が作成されたことを確認してください。
作成された plugin.yml を、画面の右側へドラッグアンドドロップしてください。
画面右側で plugin.yml の編集画面が開きます。
plugin.yml に、下記の3行を書いてください。
name: (あなたのプラグイン名) main: (作成したパッケージ名).(作成したメインクラス) version: (あなたのプラグインのバージョン)
このチュートリアルでは、次のように作成します。
name: TutorialPlugin main: my.test.plugin.tutorialplugin.TutorialPlugin version: 0.0.1
plugin.yml の詳細な内容一覧は、plugin.ymlの設定一覧 をご参照ください。
onEnable()メソッドとonDisable()メソッド
このファンクションは、プラグインが有効/無効になったときに呼ばれます。
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。
onEnable()は、プラグインがBukkitから読み込まれるときに最初に呼ばれ、プラグインを実行するために必須です。
onEnable()とonDisable()の基本
前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。
public void onEnable(){ } public void onDisable(){ }
現時点では、このメソッドは何も行いません。また、エラーが発生する事にも気付くでしょう。このエラーは、メインクラスが、プラグインの機能を継承(extends)する必要がある事を示しています。当クラスの定義文を、下記のように変更しましょう。
これを・・・
Class <classname> {}
このように変更する。
Class <classname> extends JavaPlugin {}
すると、前述の追加コードが赤の下線でハイライト表示され、何かが間違っていることを通知してくるはずです。このハイライト部にマウスを重ねると、下記のようなポップアップが表示されるので、「'JavaPlugin'をインポートします(org.bukkit.plugin.java)」を選択します。
もしくは、
import org.bukkit.plugin.java.JavaPlugin;
をコード上部の定義部に記述する事でも同様の事が行えます。
Loggerを利用したメッセージ出力
始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。この処理として、ログ出力機能(Logger)をメインクラスに定義して初期化します。
Logger log = this.getLogger();
その後、onEnable()メソッドに、プラグインが動作している事を通知するメッセージを出力する処理を記述します。
log.info("Your plugin has been enabled.");
onDisable()メソッドについても同等の記述を行います。ここまでのコーディングによって、メインクラスは下記の様な内容になっているはずです。
package me.<yourname>.<pluginname>; import java.util.logging.Logger; import org.bukkit.plugin.java.JavaPlugin; public class <classname> extends JavaPlugin { Logger log; public void onEnable(){ log = this.getLogger(); log.info("Your plugin has been enabled!"); } public void onDisable(){ log.info("Your plugin has been disabled."); } }
イベントとリスナ
- 新しいEventSystemの使い方を参照して下さい
コマンド
onCommand()メソッド
さて、どのようにイベントを登録しいつ発生するかについて理解しました。 しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。 このメソッドは、plugin.ymlに設定したプラグインのコマンドが実行されたときに呼び出されます。 今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。
コマンド名は、Bukkitや他のプラグインが提供しているものや、 提供するであろうものとの重複を避け、 ユニークなものとなるように考慮して下さい。 具体的には、"give"コマンドは既にいくつかのプラグインで利用されています。 もし独自に"give"コマンドを実装した場合は、 "give"コマンドを実装している他のプラグインとの互換性は無くなります。
onCommand()は、常にboolean型の値としてtrue,falseのどちらかを戻り値として返さねばなりません。 trueを返した場合は、情報表示のためのイベントは発生しません。 falseを返した場合は、プラグインファイルを"usage: property"に戻し、コマンドを実行したプレイヤーに、コマンドの利用方法を通知するメッセージを表示します。
onCommand()を利用する際は、4つのパラメータを登録しなければなりません。
- CommandSender sender - コマンドの発行元
- Command cmd - 実行されたコマンドの内容
- String commandLabel - 利用されたコマンドエイリアス
- String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)
コマンドの設定
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){ // プレイヤーが「/basic」コマンドを投入した際の処理... if(cmd.getName().equalsIgnoreCase("basic")){ // 何かの処理 return true; // コマンドが実行された場合は、trueを返して当メソッドを抜ける。 } return false; // コマンドが実行されなかった場合は、falseを返して当メソッドを抜ける。 }
onCommand()をコーディングする際は、上記の例のように、メソッドの最終ステップにfalseをreturnする行を記述するのが良い方法です。
falseが返る事で、plugin.yml(下記参照)内に記述されたメッセージが表示され、
onCommand()処理が正しく動作しなかった事をメッセージから検知できるため、動作確認の助けになるからです。
逆に、メソッドの最後でtrueを返す処理構造にしてしまった場合は、onCommand()内の各処理に対して、
処理結果チェック処理と結果が不正である場合にfalseを返すような、同じ処理を何度も記述する必要が出てきますので、大変な無駄となります。
なお、コード「.equalsIgnoreCase("basic")」のパラメタ「basic」は、大文字小文字の区別はありません。
plugin.ymlへのコマンドの追加
コマンドを用意する際は、plugin.ymlファイルにも定義を追加する必要があります。plugin.ymlの最後に次の行を追加します。
commands: basic: description: This is a demo command. usage: /<command> [player] permission: <plugin name>.basic permission-message: You don't have <permission>
- basic - コマンド名
- description - コマンドの説明文
- usage - onCommand()がfalseを返した際に、コマンド実行ユーザに向けて表示されるメッセージの内容。コマンドの使い方を明解に書いてください。
- permission - 当コマンドの動作に必要なプラグインの設定を、コマンド実行ユーザに知らせるメッセージ。(主に、コマンド実行に必要なpermissionを書きます)
- permission-message - コマンド実行権限を持たないユーザがコマンドを実行した場合に、その旨を同ユーザに知らせるメッセージ。
- Note: ymlファイルには、1個タブを2個のスペースで記述する必要があります。タブ文字は構文エラーとなるため利用できません。
コンソールコマンドとプレイヤーコマンド
察しの良い人は、CommandSender sender
パラメタに着目しているかもしれません。 CommandSender
クラスは、プラグイン開発者に2つの点で有用な、Bukkitが提供するインタフェースです。
CommandSenderインタフェースの実装: Player
と ConsoleCommandSender
(正確には Player
もインタフェース)です。
プラグインを作る際は、次の2点に注意しながら作る事が非常に良い方法です。
- プラグインが動作している事をサーバコンソールで確認しながら作る事
- ログインプレイヤー向けのコマンドが、実際にログインしているプレイヤーのみ実行できる事
プラグインは、senderがプレイヤーではない場合(例えばコンソールからプラグインのコマンドが投入された場合)であっても、サーバコンソールには明解でシンプルな動作結果を返します。(例えば天気を変えるコマンドの実行結果は、ゲーム内では無くサーバコンソール上のメッセージから確認すれば間違いありません)
記述例:
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { Player player = null; if (sender instanceof Player) { player = (Player) sender; } if (cmd.getName().equalsIgnoreCase("basic")){ // If the player typed /basic then do the following... // do something... return true; } else if (cmd.getName().equalsIgnoreCase("basic2")) { if (player == null) { sender.sendMessage("this command can only be run by a player"); } else { // do something else... } return true; } return false; }
この例では、basic コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、basic2 コマンドは、ログインプレイヤーしか実行できません。 一般的に、コマンドはプレイヤーとサーバコンソールの両者に実行を許可しますが、投入されたコマンドの実行可否をチェックする必要があるコマンドについては、ログインプレイヤー向けのコマンドとして実装されるべきでしょう。
つまり、プレイヤーに依存する処理(テレポートコマンドやアイテムを与えるコマンド等は、対象となるプレイヤーが必要です)を行うコマンドが、プレイヤーコマンドとして実装されるべきものと言えます。
凝ったコマンドを作りたい場合、コマンドのパラメタ(上記例では args
パラメタ)を利用して独自の処理実装を行うことができます。例えば、プレイヤー名を指定したテレポートコマンドのみが、サーバコンソールから実行できる実装にする等が考えられます。
- 訳者補記: コマンド実装方法には、プレイヤー向け・サーバコンソール向けの2種類があり、それぞれの区別はどのような考え方で行うべきかについて説明しています。
CommandExecutorのクラス分割
上記の例では、onCommand()メソッドをプラグインのメインクラスに記述していました。小さなプラグインでは良い方法ですが、大きなプラグインに拡張していくのであれば、適切なクラスを作成してそちらに配置するべきです。幸い、難しい事ではありません:
- プラグインのpackage内に、MyPluginCommandExecutor のような名前で新規のコマンド実行クラスを作成する(当然、MyPluginはあなたのプラグイン名に合わせて変えましょう)。
- MyPluginCommandExecutorに、BukkitのCommandExecutorインタフェースを実装(implements)させる。
- プラグインのonEnable()メソッドの処理で、MyPluginCommandExecutorクラスのインスタンスを生成する。
- MyPluginCommandExecutorインスタンスをパラメタとして、処理
getCommand("basic").setExecutor(myExecutor);
を実行させる。
- Note:
"basic"
は実行されたコマンドであり、myExecutor
はMyPluginCommandExecutorインスタンスです。
とっても良い具体例:
MyPlugin.java (the main plugin class):
private MyPluginCommandExecutor myExecutor; @Override public void onEnable() { // .... myExecutor = new MyPluginCommandExecutor(this); getCommand("basic").setExecutor(myExecutor); // ... }
MyPluginCommandExecutor.java:
public class MyPluginCommandExecutor implements CommandExecutor { private MyPlugin plugin; public MyPluginCommandExecutor(MyPlugin plugin) { this.plugin = plugin; } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { // ... implementation exactly as before ... } }
どのように、メインプラグインのインスタンスがMyPluginCommandExecutorのコンストラクタを実行するかに注目しましょう。
この節で紹介した方法により、メインのonCommand()メソッドが巨大で複雑になったとしても簡単に整理する事ができ、結果として、プラグインのメインクラスを複雑化させずに、処理分割する事ができます。
- Note: プラグインが複数のコマンドを持つ場合、個々のコマンドに対応するCommandExecutorをコーディングする必要があります。
- 訳者補記: 積極的に、大きな処理は小さく分割していきましょう。そのための仕組みをBukkitが用意しています。という事を教示しています。
堅牢なonCommandの記述
onCommand()メソッドを記述する際、パラメタの利用に関して、
決め付けて掛かってはいけない事がありますので念頭において下さい。
これらに留意した処理を記述する事で、堅牢なonCommand()をコーディングする事ができます。
senderの内部データ型をチェックする
senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。
処理例:
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){ if ((sender instanceof Player)) { // doSomething } else { sender.sendMessage(ChatColor.RED + "You must be a player!"); return false; } Player player = (Player) sender; return false; }
コマンドのパラメタ長をチェックする
senderインスタンスは、妥当なパラメタを持っているとは限りません。パラメタ長をチェックして下さい。
処理例:
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){ if (args.length > 4) { sender.sendMessage(ChatColor.RED + "Too many arguments!"); return false; } if (args.length < 2) { sender.sendMessage(ChatColor.RED + "Not enough arguments!"); return false; } }
プレイヤーがオンラインである事を確認する
特定のプレイヤーのPlayerインスタンスを利用したい場合、
必ずそのプレイヤーがオンラインである必要があります。
オンラインであるかどうかをチェックして下さい。
処理例:
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){ Player other = (Bukkit.getServer().getPlayer(args[0])); if (other == null) { sender.sendMessage(ChatColor.RED + args[0] + " is not online!"); return false; } return false; }
オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的にはOfflinePlayer
クラスを利用して行う事ができます。
プラグインのConfiguration/Settings
- 新しいConfigurationの使い方を参照して下さい
権限
Bukkitのパーミッション(権限)APIの利用は、簡単ではありません。
プレイヤーが特定の権限を持っているかどうかを調べる処理は次のようになります:
if(player.hasPermission("some.pointless.permission")) { //Do something }else{ //Do something else }
権限がセットされているか、いない(Javaのnullと同等)かを調べる処理は次のようになります:
boolean isPermissionSet(String name)
なぜグループの概念が存在しないかと思った方もいるでしょうが、 そもそも、権限にはグルーピングの概念は不要なのです。
元々、グループの主な用途はチャットメッセージをフォーマッティングする事にありました。
これは権限の機能を利用してもっと簡単に行えます。
例えば、チャットプラグインの設定において、権限とプレフィクスの関連を定義する事が該当します。
具体的には、権限"someChat.prefix.admin"をプレフィクス[Admin]に対応させる定義を行い、
プレイヤーがチャットで発言する度に、プレイヤー名の先頭に[Admin]が付くようになる機能が挙げられます。
他にも、グループに所属する複数のユーザに、
メッセージを送信するような機能を実現するために利用する事が考えられます。
この例を、権限を利用した処理で記述すると以下のようになります:
for(Player player: getServer().getOnlinePlayers()) { if(player.hasPermission("send.recieve.message")) { player.sendMessage("You were sent a message"); } }
さて、依然として
「グループを利用せずに、複数のプレイヤーに権限をセットする良い方法は何なのか?」が疑問かと思いますが・・・
BukkitのAPI自体は、グループの概念を提供していません。
グループの概念を利用するためには、permissionsBukkitのような
グループ権限の機能を提供するプラグインを利用する事になります。
とどのつまり、このAPIはインタフェース(Interface)であり、実装(Implementation)ではないのです。
権限の設定
権限を利用して細かな制御を行いたい場合は、
デフォルト権限と、子権限の設定をplugin.ymlに追記する事を検討して下さい。
この2種類の設定は、オプション(必須ではなく完全に任意で利用される)項目ではありますが、お勧めします。
下記は、plugin.ymlの最後に追加する形で設定する権限です:
permissions: doorman.*: description: Gives access to all doorman commands children: doorman.kick: true doorman.ban: true doorman.knock: true doorman.denied: false doorman.kick: description: Allows you to kick a user default: op doorman.ban: description: Allows you to ban a user default: op doorman.knock: description: Knocks on the door! default: true doorman.denied: description: Prevents this user from entering the door
まず、プラグインが利用する各権限を、permissionsノードの子ノードとして定義します。
それぞれの権限はオプションとして、概要(description)、デフォルト値、子ノードを持ちます。
デフォルト権限
プレイヤーが権限を持たない場合、 hasPermission はデフォルトではfalseを返します。
plugin.yml中のデフォルトのノードに対して、下記の4つのどれか1つを設定する事で、
この挙動を変更する事ができます:
- true - デフォルトで、権限を持つ(=hasPermissionがtrueを返す)。
- false - デフォルトで、権限を持たない(=hasPermissionがfalseを返す)。
- op - プレイヤーがOPであれば、権限を持つ(=hasPermissionがtrueを返す)。
- not op - プレイヤーがOPでなければ、権限を持つ(=hasPermissionがtrueを返す)。
子権限
恐らく今までに、* 権限を利用してサブ権限を割り当てた事があると思います。
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、
高い柔軟性を提供しています。
下記はその実装例です:
permissions: doorman.*: description: Gives access to all doorman commands children: doorman.kick: true doorman.ban: true doorman.knock: true doorman.denied: falsedoorman.*権限は、いくつかの子権限を含んでいます。 doorman.*権限がtrueである場合に、 その子権限はplugin.ymlに定義された値(trueかfalse)をデフォルト権限として機能します。 doorman.*権限がfalseである場合は、 その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。
独自の権限設定
独自に権限の仕組みを提供するプラグインを開発したい場合、 権限プラグインの開発方法を参考にして下さい。
スケジューリングタスクとバックグラウンドタスク
現在、Minecraftサーバはゲームロジックのほとんどがシングルスレッドで稼動しています。
このため、発生する個々の処理はごく小さいものにする必要があります。
プラグイン中に複雑なコードが存在して、それが適切に処理されないような場合は、
ゲームロジックに多大なラグや処理遅延を発生させる原因となります。
幸運なことに、Bukkitはプラグインに対してスケジューリングのためのコーディング方法を提供しています。
どこかのタイミングで一度だけRunnableタスクを実行したり、
小さなタスクに分けた定期的な繰り返しであったり、
新規に独立したスレッドを派生させたり・・・
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。
詳しくは、スケジューラのプログラミング中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。
ブロックの操作
ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、
まずはそのブロックを取得した上で、変更を加えることになります。
PlayerMoveイベントの処理中でこれを行う例を示します:
public void onPlayerMove(PlayerMoveEvent evt) { Location loc = evt.getPlayer().getLocation(); // プレイヤーからその位置を得る World w = loc.getWorld(); // 位置からワールドを得る loc.setY(loc.getY() + 5); // 位置のY座標を+5する Block b = w.getBlockAt(loc); // ワールド上の指定位置のブロックを得る b.setTypeId(1); // ブロックのタイプIDに1をセットする }
このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、
プレイヤーの5ブロック上方のブロックがStone(TypeIdが1のブロック)に変化する動作として見えるでしょう。
ブロック取得までの流れは・・・
- 取得したプレイヤーの位置から、ワールドを取得する
- 位置のY座標を+5する
w.getBlockAt(loc);
で、ワールドにおける位置に存在するブロックを得る
となります。
位置(loc)中のブロックインスタンス(b)に対して、
IDを変えたり、ブロックデータ自体を変更する事もできます。
また、ブロックの種類を表すデータはbyte型であるため、
byte型にキャストしたデータを与えた指定ができます。
例えば、b.setData((byte)3);
です。
建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う事ができます。
例えば、固体ブロックによる立方体を生成する処理は、
3階層にネストしたforループ処理によって記述できます。
public void generateCube(Location point, int length){ // public visible method generateCube() with 2 parameters point and location World world = point.getWorld(); int x_start = point.getBlockX(); // Set the startpoints to the coordinates of the given location int y_start = point.getBlockY(); // I use getBlockX() instead of getX() because it gives you a int value and so you dont have to cast it with (int)point.getX() int z_start = point.getBlockZ(); int x_lenght = x_start + length; // now i set the lenghts for each dimension... should be clear. int y_lenght = y_start + length; int z_lenght = z_start + length; for(int x_operate = x_start; x_operate <= x_length; x_operate++){ // Loop 1 for the X-Dimension "for x_operate (which is set to x_start) //do whats inside the loop while x_operate is //<= x_length and after each loop increase //x_operate by 1 (x_operate++ is the same as x_operate=x_operate+1;) for(int y_operate = y_start; y_operate <= y_length; y_operate++){// Loop 2 for the Y-Dimension for(int z_operate = z_start; z_operate <= z_length; z_operate++){// Loop 3 for the Z-Dimension Block blockToChange = world.getBlockAt(x_operate,y_operate,z_operate); // get the block with the current coordinates blockToChange.setTypeId(34); // set the block to Type 34 } } } }
このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。
ただし、セットするブロックのIDはゼロ(=air)になりますね。
プレイヤーインベントリの操作
この節では、プレイヤーのインベントリ操作を特に扱っていますが、
チェストのインベントリの操作にも適用できますので、必要なら応用して下さい(゚∀゚)
インベントリ操作のシンプルな例:
public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); // Joinしたプレイヤー PlayerInventory inventory = player.getInventory(); // プレイヤーのインベントリ ItemStack diamondstack = new ItemStack(Material.DIAMOND, 64); // 山積みのダイヤモンド! if (inventory.contains(diamondstack)) { inventory.addItem(diamondstack); // プレイヤーインベントリに山積みのダイヤモンドを加える player.sendMessage(ChatColor.GOLD + "よく来たな!もっとダイヤモンドをくれてやろう、このとんでもない成金め!!"); } }
さて、onPlayerJoin()メソッドの先頭で
player, inventory、diamondstack変数を用意して下さい。
inventoryはプレイヤーのインベントリで、diamondstackは(アイテムとしての)64個のダイヤモンドです。
次に、プレイヤーのインベントリがダイヤモンドを含んでいるかをチェックします。
プレイヤーがダイヤモンドをインベントリに所持している場合、
inventory.addItem(diamondstack)
処理にて
同プレイヤーのインベントリに別のスタックを与え、黄金色のメッセージを送信します。
インベントリ操作は全然難しくありません。
ダイヤモンドのスタックを削除したい場合でも単純に、
inventory.addItem(diamondstack)
をinventory.remove(diamondstack)
に置き換えるだけです。
(メッセージにも少しイタズラしておきましょう)
アイテムの操作
アイテムはスタックという単位で操作します。
スタックデータに対する全ての操作は、ItemStackクラスの仕様を参照して下さい。
エンチャント
アイテムに対するエンチャントに触れる前に、Item Code と EIDを見てから以下の解説を読んでください。
エンチャントは、Enchantmentクラスが受け持っている機能ですが、
Enchantmentクラス自体は抽象クラスであるため、インスタンス化(new Enchantment())出来ません。
エンチャントはEnchantmentWrapperクラスから利用する必要があるからです。
元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。
Bukkitサーバ自体が、エンチャント不可能なアイテムに対して、エンチャント情報を付与する仕組みを備えていないからです。
つまり、Fire AspectedなStick(火のエンチャントが付いた木の棒)はあり得ません。
int itemCode = 280; // StickのItemID int effectId = 20; // 効果Fire AspectのEID int enchantmentLevel = 100; // エンチャントのレベル ItemStack myItem = new ItemStack(itemCode); // 木の棒のインスタンスを生成する Enchantment myEnchantment = new EnchantmentWrapper(effectId); // エンチャント効果のインスタンスを生成する myItem.addEnchantment(myEnchantment, enchantmentLevel); // 木の棒にFireAspectを付与する(ただし付与は成功しない)
Matadata
Bukkit では、プラグインの開発をより簡単にするため、Playerクラス、Entityクラス、Worldクラスに紐づく追加データをMetadataという形式で管理できるようになっています。 今までは、それぞれのプラグイン内で、Player、Entity、World などをキーとしたHashMap型の変数内で管理していたと思いますが、それをMetadataで置き換えすることができます。 Metadataのデータは、全てMetadatableのメンバーで構成されます(javadocも参照してください)。 動作は非常に単純です。 Metadatableクラスは、それぞれのインスタンスが自分のMetadataのHashMapを持っています。 つまり例えば、経済プラグインを作る場合、HashMap<Player, Double> のようなデータをプラグイン内で持つ必要はありません。 プレイヤーにMetadataを直接設定すればよいのです!
Metadataを使うメリット
- Metadataは全てBukkit側で管理されます。プラグイン側で管理する必要がありません。
- プラグイン間で共通にアクセスできるので、データの共有に使用できます。
Metadataを使うデメリット
- データの取得・設定を行うときに、ひと手間が必要になります。
- Bukkitが停止すると、全てのMetadataが消えます。
Metadataの使い方
/** * PlayerにMetadataを設定するサンプルメソッドです。 * @param player 対象プレイヤー * @param key Metadataのキー名 * @param value Metadataの値 * @param plugin プラグインクラス */ public void setMetadata(Player player, String key, Object value, Plugin plugin) { player.setMetadata(key, new FixedMetadataValue(plugin, value)); } /** * PlayerからMetadataを取得するサンプルメソッドです。 * @param player 対象プレイヤー * @param key Metadataのキー名 * @param plugin プラグインクラス * @return Metadataの値 */ public Object getMetadata(Player player, String key, Plugin plugin) { List<MetadataValue> values = player.getMetadata(key); for (MetadataValue value : values) { if (value.getOwningPlugin().getDescription().getName() .equals(plugin.getDescription().getName())) { return value.value(); } } return null; }
- Note: もしあなたが、必ず boolean、int、String としてMetadataの値を取得したいのであれば、asBoolean()、asInt()、asString() メソッドを使うことで、キャストせずに直接取得が可能です。
データベース
時にフラットファイルは、データベースとして利用するには不足があるでしょう。 Linux/Mac/Windowsのマシンにおいて、もっとも汎用的なデータベースはSQL(Structured Query Language)です。
ソフトウェアとしてのSQLは、個々のデータを格納するセルと、それらを識別するための列およびヘッダから成るデータベースを生成します。 会計用ソフトウェア(たとえばエクセルのような)の表が、より便利になったモノを思い浮かべてください。 SQLは、この表形態のデータに、データ同士の整合性の確保を目的とした独自のルール付けが行え、 かつ、フラットなファイルよりも高速で高機能なデータ利用のための方法も提供します。
Bukkitは、SQLの標準的な機能を利用するための仕組みを提供しています。 残念なことに、SQLには複数の種類が存在し、それぞれの設定方法と利用方法が少しずつ異なっています。 各自のニーズに応じて、好きな種類のSQLを選んで利用してください。(プラグインについても、利用するデータベースの種類に応じて複数の接続・利用方法が利用できます)
SQLite
Alta189は、ダウンロード・インポートによって導入や移動が容易に可能なSQLiteを、Bukkitのプラグインで利用するためのライブラリを使う方法のチュートリアル、fantastic SQLite tutorialを書きました。
SQLの構文については、次に紹介する動画を一度見てみる事をお勧めします。簡単な内容なので短時間で済みます。SQL Tutorials @W3Schools
SQLiteは、その稼動のためにサーバを構築する必要がないため、シンプルさにおいて非常に優れています。 新規にデータベースとその中のテーブルを作成する手順は少量です。また、データのバックアップも、1個のデータベースファイルをバックアップするだけです。ただし、データ間の整合性の確保や柔軟性、データ件数が100万を超えるような膨大なデータの扱いにおいては、多少弱い面もあります。
とはいえSQLiteは、SQLデータベースを利用する新規プラグインの開発作業を、迅速で簡単にしてくれるメリットがあります。また、サーバ向けの大規模なSQLの予習のためにも有用です。
MySQL
もうひとつ、人気があるSQLにMySQLというものがあります。 SQLiteよりもサーバ寄りの用途をもった種類のSQLで、多くの有名な企業の業務システムや、一日に100万アクセスを捌くようなウェブサイトが、この機能に依存しています。ただし、チューニング可能な範囲や機能が多岐に渡るため、セキュリティ上のリスクが、データベース管理者のMySQLへの習熟度に大きく左右されます。
プラグインからMySQLを利用する処理のコーディング自体は、小規模なSQLiteからMByte単位のOracleのデータベースに対するものと、大差ありません。しかし、サーバ用途のデータベースの管理作業は、どんどん増えていくものです。使い続ける内に、データベース利用者のアカウントや権限の設定作業を行う事になっていくでしょう。また、データのバックアップやバックアップからの復旧(Rollbackと呼ぶ)作業のために、SQL文のスクリプトを書く事にもなるはずです。
プラグインの配布
- Note: 当セクションは、日本語圏の読者向けの資料として外部の紹介サイトを独自に追加しています。
プラグインのコーディング後に行うべき事は、サーバへインストールするためのJarファイルを、ソースコードから作成する事です。これはどのように行えば良いでしょう?
- まずは、サーバのセットアップを参考にしながらローカルマシン上にBukkitサーバを起動可能な状態で設置して下さい。(とりあえずは[minecraft公式サーバ]と[bukkitサーバ]を取得して、bukkitサーバを起動コマンドで起動可能な状態にするだけで良い)
- 次に、プラグインをJar形式でエクスポートするための画面を開きます。(Eclipseを例にした場合、プラグインのプロジェクトを選択した状態で、パッケージエクスプローラ上のプロジェクトの右クリックか、ファイル > エクスポートの選択で表示される機能)
- 開いたエクスポート画面上で"Java", "JARファイル"を選択し、次へボタンを押下し、下記のような画面に遷移します。
- 画面左側のメニューで、プラグインのソースコードが存在するフォルダ(この例ではsrc)が選択されている事を確認します。
- 画面右側のメニューで、ファイル名がドットで始まるファイルの選択を解除します(この例では.classpath, .gitgnore, .projectの3ファイル)。これらはEclipseが利用するファイルでありプラグインには不要です。
- プラグインを動作させるために重要な事ですが、plugin.ymlが選択されている事も確認します。
- 最後に、任意の場所にJARファイルをエクスポート(書き出し)します。
プラグインのコードとplugin.ymlに不備が無ければ、エクスポートしたJarファイルはすぐにBukkitプラグインとして動作します。Jarファイルを、Bukkitサーバの"plugins"フォルダの中に配置し、Bukkitサーバを起動し、プラグインの動作確認をしてみましょう。なお、ローカルマシン上で起動するBukkitサーバへは、Minecraftクライアントのマルチプレイヤーサーバの接続先IPアドレスに"localhost"を指定して接続する事でログインできます。
もしプラグインが上手く動かず、それがどうしても自分で解決できない場合は、当Wikiやその原文をもう一度よく読み、それでも駄目ならBukkitプラグイン開発者フォーラム(英語圏), bukkitdev Bukkit公式サイトの開発者向けIRCチャンネル(英語圏), マインクラフト非公式日本ユーザフォーラムの関連トピック(日本語圏), 当WikiのMOD制作関連IRCチャンネル(日本語圏)をたずねてみて下さい。有用なプラグインが作れたら、dev.bukkitに登録し、プラグインを広く公開する事を検討してみて下さい。他のBukkitユーザ・開発者に貢献する事ができます。
ヒントとノウハウ
CraftBukkit APIは、すばらしい機能をたくさん提供しています。 下記に、面白い効果を実現するコードを示します。
プレイヤーに着火する
下記は、指定されたプレイヤーに着火するサンプルです。例えば /ignite Notch と実行すると、Notchが燃えます!
@Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { // equals() の代わりに equalsIgnoreCase() を使うと、大文字小文字に関係なく、 // 文字列の一致を確認できます。"ignite"でも"IgNiTe"でも指定可能になります。 if (cmd.getName().equalsIgnoreCase("ignite")) { // コマンドのパラメータに、燃やすプレイヤーが指定されているかどうかを // 確認します。 if (args.length != 1) { // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを // コマンド実行者の画面に表示します。 return false; } // 燃やすプレイヤーを取得します。 Player target = Bukkit.getServer().getPlayer(args[0]); // 対象プレイヤーが、オンラインかどうかを確認します。 if (target == null) { sender.sendMessage(args[0] + " というプレイヤーが見つかりません!"); return true; } // 対象プレイヤーを、1000tick(=50秒) の間、燃えるようにします。 target.setFireTicks(1000); return true; } return false; }
プレイヤーを殺す
同じ要領で、プレイヤーを殺害するコマンドの例を紹介します。 onCommand()メソッドに記述します:
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){ if(cmd.getName().equalsIgnoreCase("KillPlayer")){ Player target = sender.getServer().getPlayer(args[0]); // 対象プレイヤーがオンラインかどうかを確認します。 if (target == null) { sender.sendMessage(args[0] + " というプレイヤーは見つかりません!"); return true; } target.setHealth(0); } return false; }
上記の拡張版として、プレイヤーを爆死させる処理を下記に示します:
float explosionPower = 4F; //This is the explosion power - TNT explosions are 4F by default Player target = sender.getWorld().getPlayer(args[0]); target.getWorld().createExplosion(target.getLocation(), explosionPower); target.setHealth(0);
爆発を起こす
このコードは、TNTの爆発と同様の音とヴィジュアルを再現します。 これは、TNTの爆発効果は無効化しつつ、音とヴィジュアル効果を発生させる処理に転用できます。
@EventHandler public void onExplosionPrime(ExplosionPrimeEvent event) { Entity entity = event.getEntity(); // このイベントは、点火されたTNTにより発生したのかどうかを確認します。 // (つまり、TNTの爆発はこれで無効化されますが、クリーパーの爆発は無効化されません) if (entity instanceof TNTPrimed) { entity.getWorld().createExplosion(entity.getLocation(), 0); } }
プレイヤーを非表示にする
これは指定したプレイヤーから自分を非表示にするサンプルです。 指定したプレイヤー以外のプレイヤーからは、自分が見えたままになっています。
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if(cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) { // コマンド実行者がプレイヤーかどうかを確認します。 if (!(sender instanceof Player)) { sender.sendMessage("このコマンドはゲーム内から実行してください!"); return true; } // sender instanceof Player の検査が終わっているので、Playerクラスへ安全にキャストできます。 Player s = (Player) sender; // 指定されたプレイヤーを取得します。 // 指定されたプレイヤーがサーバーに接続していない場合、targetはnullになります。 Player target = Bukkit.getServer().getPlayer(args[0]); if (target == null) { sender.sendMessage("Player " + args[0] + " is not online."); return true; } // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。 target.hidePlayer(s); return true; } return false; }
クリックした場所に雷を落とす
下記のサンプルは、釣竿を持ってクリックしたときに、クリックした場所を取得して、その場所に雷を落とします。
@EventHandler public void onPlayerInteractBlock(PlayerInteractEvent event) { Player player = event.getPlayer(); if (player.getItemInHand().getTypeId() == Material.FISHING_ROD.getId()) { // プレイヤーが見ている場所に雷をおとします。 // 距離が200ブロック以内なら、ターゲットブロックを取得することができます。 player.getWorld().strikeLightning(player.getTargetBlock(null, 200).getLocation()); } }
リクエストに応じて書かれた記事
mavenを利用したプラグイン開発
gitのリポジトリ上にあるBukkitPluginArchetypeをcloneし、それをビルドする:
git clone git://github.com/keyz182/BukkitPluginArchetype.git cd BukkitPluginArchetype mvn clean install
作成するプラグインのフォルダに移動して下記のコマンドを実行します:
mvn archetype:generate -DarchetypeCatalog=local
プロンプトに表示されたリストから下記を選択します:
uk.co.dbyz:bukkitplugin (bukkitplugin)
GroupIDには筆頭(プラグインのトップ階層)としたいJavaパッケージ名を指定し、ArtifactIDにはパッケージ名の末端の名称(Jarファイルのような成果物の名称として利用されます)を入力します。そして、確認メッセージにY<enter>で応答します。
例:
Define value for property 'groupId': : uk.co.dbyz.mc Define value for property 'artifactId': : plugin Define value for property 'version': 1.0-SNAPSHOT: Define value for property 'package': uk.co.dbyz.mc:
ArchetypeIDに指定した文字列と同名のフォルダが生成され、配下にsrcフォルダとpom.xmlファイルが配置されます。
src/main/java/<package>フォルダに存在する<archetypeid>CommandExecuter.javaファイルを開き、コード
//Do Something
の部分に書きのコードを記述します:
Player player = (Player) sender; player.setHealth(1000f);
ベースのフォルダに移動して下記を実行します:
mvn clean package
ダウンロード処理が走りますが、他の作業を並行して行っても大丈夫です。 clean package処理が完了すると、targetフォルダの中に<archetypeid>-1.0-SNAPSHOT.jarファイルが生成されます(これがビルドされたプラグインのJarファイルです)。このファイルをBukkitのpluginsフォルダへコピーして、Bukkitサーバを起動して下さい。
ゲーム内で/<archetypeid>コマンドを実行すると、そのプレイヤーのHealthが全快します。
プラグインのサンプル兼雛形
この内容について質問がある場合、遠慮なくAdamki11sかBukkitDevのIRCチャンネル(当Wikiの原文を掲載しているサイトのIRCチャンネルです)で聞いてください。
- Note: 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。その点を考慮して必ず原文にも目を通してから質問して下さい。