提供: Minecraft Modding Wiki
移動先: 案内検索
(ページの白紙化)
(220.151.7.77トーク)による編集をUcchyによる直前の版へ差し戻しました)
1行目: 1行目:
 +
本ページの内容は、[http://wiki.bukkit.org/ Bukkit Wiki]の[http://wiki.bukkit.org/Plugin_Tutorial Plugin Tutorial]を和訳した物となります。(一部は省略しています)<br />
 +
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。<br />
  
 +
== 始めに ==
 +
:'''重要''': 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
 +
:'''重要''': 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
 +
:'''重要''': 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。当ページの内容を受けて原著者へ何らかのアクションを行う場合、その点を考慮して必ず原文にも目を通して下さい。
 +
 +
このチュートリアルは、Bukkitプラグインを開発するための基本的なノウハウを網羅しております。
 +
 +
Javaを習得する、IDEで開発環境を構築する、などの非常に基本的な部分から説明が始まっていますが、既にご理解頂いている場合は読み飛ばしていただいて構いません。
 +
 +
== Javaの習得 ==
 +
:'''Note''': 当記事の一部は原文の訳ではありません、日本語読者向けに独自の内容を記述しています。
 +
 +
このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。<br/>
 +
Javaの基礎知識が少ない方は、下記の情報に触れてください。
 +
 +
*[http://docs.oracle.com/cd/E26537_01/tutorial/index.html Oracleの記事] (現在のJavaの公式サイト)
 +
*[http://www.tohoho-web.com/java/index.htm とほほのJava入門] - 基礎をみっちり解説している
 +
*[http://www.kab-studio.biz/Programing/OOPinJava/ Javaのオブジェクト指向入門] - 継承や実装等のオブジェクト指向用語が分からない人向け
 +
 +
Javaがどうしても理解できない場合は、この他にも様々な資料があるので探して触れてください。
 +
 +
== Javaの開発環境 ==
 +
:'''Note''': 当記事の一部は原文の訳ではありません、日本語読者向けに独自の内容を記述しています。
 +
 +
プラグインを開発する(またはJavaを学ぶ)前に、開発環境(IDE (Integrated Development Environment))をインストールして下さい。<br/>
 +
開発環境は、プラグインのコンパイルとデバッグを行うために使います。<br/>
 +
Javaの主要なIDEには、[http://www.eclipse.org/ Eclipse], [http://netbeans.org/ Netbeans], [http://www.jetbrains.com/idea/ IntelliJ IDEA] の3つがあります。<br/>
 +
EclipseはBukkit開発者の中では最も人気があり、IntelliJの方は業界内で広く利用されています。
 +
 +
あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。<br/>
 +
Eclipseのお勧めのバージョンの配布元は、[http://mergedoc.sourceforge.jp/ 日本語 Eclipse / Pleiades All in One 日本語ディストリビューション]です。<br/>
 +
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。
 +
 +
Pleiadesを利用する場合、「開発対象用 JDK」と、ビルドツールMavenが実行できるプラグイン「m2e」が同梱されているものを選択してください。<br/>
 +
(Eclipseを既に利用している場合でも、m2eを後から追加インストールすることは可能です。)<br/>
 +
以下、m2eが既に導入されている前提で説明します。
 +
 +
== Plugin用プロジェクトを始めるために ==
 +
 +
=== プロジェクトの作成 ===
 +
始めるために、新しいワークスペースを作成する必要があります。<br />
 +
Eclipseを起動し、ファイル>新規>プロジェクト... と選択して下さい。<br />
 +
 +
[[Image:create_project.png]]
 +
 +
新規プロジェクトのダイアログが開きます。Mavenフォルダを開いて、Mavenプロジェクト を選択し、次へ を押してください。
 +
 +
[[Image:create_project1.png]]
 +
 +
次のダイアログで、「シンプルなプロジェクトの作成」にチェックを入れて、次へ を押してください。
 +
 +
[[Image:create_project2.png]]
 +
 +
次のダイアログで、作成するプラグインの「グループID」と「アーティファクトID」を入力します。<br/>
 +
(アーティファクトIDとは、これから作ろうとするプラグインの名前と同義と思っていただいて構いません。)
 +
 +
グループIDは下記の方法で命名して下さい:
 +
<blockquote>
 +
* ドメインを持っているなら、そのドメインの逆引き名にしましょう。
 +
** ドメイン名が'''i-am-a-bukkit-developer.com'''の場合、パッケージ名は'''com.i_am_a_bukkit_developer'''となります。
 +
* ドメイン名を持っていない場合、下記の選択肢があります。お勧め順です。
 +
** '''Option 1''' githubやsourceforgeのようなソース管理サイトにアカウント登録します。
 +
*** githubの場合: [http://pages.github.com/ こちら]から作成したユーザページのURLを元に、'''com.github.<username>'''と命名します。
 +
** '''Option 2''' emailアドレスを元に命名します。('''<username>@gmail.com'''であれば'''com.gmail.<username>'''とします)
 +
** '''Option 3''' 重複しなさそうなパッケージ名を指定します(他者の開発物と重複する可能性が高いので非推奨)
 +
</blockquote>
 +
 +
 +
下記の名前で始まるような、他者の開発物と重複するパッケージで命名すべきでは'''ありません''':
 +
<blockquote>
 +
* org.bukkit
 +
* net.bukkit
 +
* com.bukkit
 +
* net.minecraft
 +
</blockquote>
 +
 +
ベースとなるグループIDを決めたら、次にプラグイン名を決めましょう。ここでは例として、TutorialPlugin と名付けます。
 +
 +
ダイアログには次のように入力し、完了 を押してください。
 +
 +
[[Image:create_project3.png]]
 +
 +
Eclipseの画面に戻ると、左側に TutorialPlugin プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?
 +
 +
=== Bukkit API の参照設定 ===
 +
 +
次に、左側に作成された TutorialPlugin プロジェクトの中にある、「pom.xml」というファイルをダブルクリックしてください。そして、画面右下のあたりにある「pom.xml」というタブをクリックしてください。
 +
 +
[[Image:create_project4.png]]
 +
 +
まず、Java実行環境(JRE)の参照設定をします。<br/>
 +
一番最後の行に '''</project>''' というタグがありますが、その1行上に、次の内容を挿入してください。<br/>
 +
(なお、これは Java 7 を参照してビルドするための設定です。もし Java 6 を参照してビルドしたい場合は、sourceタグとtargetタグに書かれている 1.7 のところを 1.6 に変更してください。)
 +
 +
<code>
 +
  <build>
 +
    <plugins>
 +
      <plugin>
 +
        <groupId>org.apache.maven.plugins</groupId>
 +
        <artifactId>maven-compiler-plugin</artifactId>
 +
        <configuration>
 +
          <nowiki>&lt;</nowiki>source>1.7</source>
 +
          <target>1.7</target>
 +
        </configuration>
 +
      </plugin>
 +
    </plugins>
 +
  </build>
 +
</code>
 +
 +
次に、BukkitリポジトリのURL設定を追加します。<br/>
 +
下記の内容を追記してください。
 +
 +
<code>
 +
  <repositories>
 +
    <repository>
 +
      <id>bukkit-repo</id>
 +
      <url>http://repo.bukkit.org/content/groups/public/</url>
 +
    </repository>
 +
  </repositories>
 +
</code>
 +
 +
最後に、Bukkit API の参照設定を追加します。<br/>
 +
下記の内容を追記してください。
 +
 +
<code>
 +
  <dependencies>
 +
    <dependency>
 +
      <groupId>org.bukkit</groupId>
 +
      <artifactId>bukkit</artifactId>
 +
      <version>1.7.9-R0.2</version>
 +
    </dependency>
 +
  </dependencies>
 +
</code>
 +
 +
この設定では、Bukkit API 1.7.9-R0.2 が参照されます。<br/>
 +
別のバージョンを参照したい場合は、versionタグの中を変更してください。<br/>
 +
設定が可能なバージョン番号の一覧は、[http://repo.bukkit.org/content/groups/public/org/bukkit/bukkit/ こちら] を参照してください。
 +
 +
ここまで編集をすると、pom.xmlは次のようになっているはずです。確認してみてください。
 +
 +
<pre>
 +
<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>
 +
          <nowiki>&lt;</nowiki>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.7.9-R0.2</version>
 +
      <type>jar</type>
 +
      <scope>provided</scope>
 +
    </dependency>
 +
  </dependencies>
 +
</project>
 +
</pre>
 +
 +
pom.xmlの編集が完了したら、Ctrl + S でファイルを保存してください。
 +
 +
次に、プロジェクトを右クリックして、Maven>プロジェクトの更新... と選択してください。
 +
 +
[[Image:Create_project7.png]]
 +
 +
Mavenプロジェクトの更新ダイアログが開きます。そのまま OK を押してください。
 +
 +
[[Image:Create_project5.png]]
 +
 +
Eclipseが、Bukkit API のダウンロードを開始します。<br/>
 +
しばらく待つと、プロジェクトが更新され、「Maven依存関係」というところにダウンロードされたBukkit APIが表示されます。<br/>
 +
また、JREシステム・ライブラリ のところも、指定したJavaビルドバージョンが反映されていることを確認して下さい。
 +
 +
[[Image:Create_project6.png]]
 +
 +
:'''訳者補記''': ダウンロードされた bukkit-x.x.x-Rxx.jar を右クリックし、Maven > Javadoc のダウンロード を実行しておくとよいでしょう。
 +
 +
=== パッケージの作成 ===
 +
 +
次に、作成したプラグインのプロジェクトに、パッケージを追加します。<br/>
 +
「src/main/java」のところを右クリックして、新規>パッケージ と選択してください。
 +
 +
[[Image:Create_package1.png]]
 +
 +
パッケージ名は、先ほど設定したグループIDに、プラグイン名を小文字に変換した名前を後ろに付けたものが望ましいです。<br/>
 +
ここでは例として、パッケージIDに '''my.test.plugin'''、プラグイン名に '''TutorialPlugin''' を使っているので、'''my.test.plugin.tutorialplugin''' と設定します。
 +
 +
[[Image:Create_package2.png]]
 +
 +
=== メインクラスの作成 ===
 +
 +
次に、プラグインのメインクラスを作成します。<br/>
 +
メインクラスは、JavaPlugin を継承する必要があります。<br/>
 +
(逆に、メインクラス以外のクラスは、直接的にも間接的にも、JavaPlugin を継承しないようにしてください。CraftBukkit 1.7.2-R0.3 以降では、プラグインが正しく動作しなくなります。)<br/>
 +
メインクラスは、プラグイン名と同じ名前にすることが望ましいです。
 +
 +
先ほど作成したパッケージを右クリックして、新規>クラス と選択してください。
 +
 +
[[Image:Create_package3.png]]
 +
 +
名前の欄にクラス名を入力してください。<br/>
 +
スーパークラスの欄に「org.bukkit.plugin.java.JavaPlugin」と入力してください。<br/>
 +
(参照... ボタンを押して、開いたダイアログに「JavaPlugin」と入力して該当クラスを検索して選択しても構いません。)
 +
 +
完了 を押して、新規クラスが作成されたことを確認して下さい。ソースコードは次のようになっているはずです。
 +
 +
<source lang="java">
 +
package my.test.plugin.tutorialplugin;
 +
 +
import org.bukkit.plugin.java.JavaPlugin;
 +
 +
public class TutorialPlugin extends JavaPlugin {
 +
 +
}
 +
</source>
 +
 +
{{warning}} メインクラスは、コンストラクタを実行したり、新しいインスタンスを作成したりしないでください。
 +
 +
=== plugin.ymlの作成 ===
 +
 +
メインクラスが作成できたら、次は plugin.yml ファイルを作成します。<br/>
 +
plugin.yml ファイルは、プラグインとしてBukkitに読み込みされるときに、プラグインの設定を記述しておくファイルです。<br/>
 +
必須のファイルですから、必ず作成してください。
 +
 +
プロジェクトの ''src/main/resources'' を右クリックして、新規>ファイル を選択してください。
 +
 +
[[Image:Create_pluginyml1.png]]
 +
 +
開いたダイアログの ファイル名 の欄で、「plugin.yml」と入力し、完了を押してください。
 +
 +
[[Image:Create_pluginyml2.png]]
 +
 +
''src/main/resources'' のところに、''plugin.yml'' が作成されたことを確認してください。<br/>
 +
作成された ''plugin.yml'' を、画面の右側へドラッグアンドドロップしてください。<br/>
 +
画面右側で ''plugin.yml'' の編集画面が開きます。
 +
 +
''plugin.yml'' に、下記の3行を書いてください。
 +
 +
<source lang="yaml">
 +
name: (あなたのプラグイン名)
 +
main: (作成したパッケージ名).(作成したメインクラス)
 +
version: (あなたのプラグインのバージョン)
 +
</source>
 +
 +
このチュートリアルでは、次のように作成します。
 +
 +
<source lang="yaml">
 +
name: TutorialPlugin
 +
main: my.test.plugin.tutorialplugin.TutorialPlugin
 +
version: 0.0.1
 +
</source>
 +
 +
{{note}} メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。
 +
 +
 +
plugin.yml の詳細な内容一覧は、[[plugin.ymlの設定一覧]] をご参照ください。
 +
 +
== onEnable()メソッドとonDisable()メソッド ==
 +
このメソッドは、プラグインが有効/無効になったときに、Bukkitから呼び出しされます。<br />
 +
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。<br />
 +
onEnable()は、Bukkitが起動するときに、プラグインが有効化されたときに呼び出されます。onDisable()は、Bukkitが停止するときに、プラグインが無効化されたときに呼び出されます。<br />
 +
 +
=== onEnable()とonDisable()の基本 ===
 +
前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。<br />
 +
<blockquote><source lang="java">
 +
package my.test.plugin.tutorialplugin;
 +
 +
import org.bukkit.plugin.java.JavaPlugin;
 +
 +
public class TutorialPlugin extends JavaPlugin {
 +
 +
    @Override
 +
    public void onEnable() {
 +
        // TODO ここに、プラグインが有効化された時の処理を実装してください。
 +
    }
 +
 +
    @Override
 +
    public void onDisable() {
 +
        // TODO ここに、プラグインが無効化された時の処理を実装してください。
 +
    }
 +
}
 +
</source></blockquote>
 +
 +
 +
現時点では、このメソッドは何も行いません。
 +
 +
=== Loggerを利用したメッセージ出力 ===
 +
 +
始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。<br/>
 +
ログを出力するには、''getLogger()''メソッドを実行してLoggerを取得し、そのinfoメソッドを呼び出します。
 +
 +
<blockquote><source lang="java">
 +
getLogger().info("onEnableメソッドが呼び出されたよ!!");
 +
</source></blockquote>
 +
 +
onDisable()メソッドについても同等の記述を行います。<br/>
 +
メインクラスに、次のように実装してみてください。
 +
 +
<blockquote><source lang="java">
 +
package my.test.plugin.tutorialplugin;
 +
 +
import org.bukkit.plugin.java.JavaPlugin;
 +
 +
public class TutorialPlugin extends JavaPlugin {
 +
 +
    @Override
 +
    public void onEnable() {
 +
        getLogger().info("onEnableメソッドが呼び出されたよ!!");
 +
    }
 +
 +
    @Override
 +
    public void onDisable() {
 +
        getLogger().info("onDisableメソッドが呼び出されたよ!!");
 +
    }
 +
}
 +
</source></blockquote>
 +
 +
ここまで作ったところで、[[#プラグインの配布|プラグインのJarファイルの発行]] を行ってプラグインを作成し、実際にCraftBukkitのpluginsフォルダへ導入して、コンソールに設定したログメッセージが表示されることを確認してみてください。
 +
 +
=== Reloadを制御する ===
 +
 +
サーバーが終了処理をするときや、開始処理をするときに限らず、
 +
サーバーが /reload コマンドにより、プラグインがdisableされenableされる動作について理解しておくことも重要です。<br/>
 +
サーバーが開始したときについてだけ初期化処理を考慮することは危険です。<br/>
 +
なぜなら、プレイヤーは既にオンライン状態で接続していますし、ワールドデータやチャンクは既にロードされていますし、他にもいろいろ想定していない違いがあります。
 +
 +
* /reload が実行されると、プラグインとしては、onDisable() が実行されて、onEnable() が実行されます。
 +
* staticで保持していないデータは、全て失われます。
 +
* 上記に書いてあるように、プレイヤーやワールドやチャンクは既にロード済みの状態です。
 +
 +
== イベントとリスナ ==
 +
 +
:: ''[[新しい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となる)
 +
 +
=== コマンドの設定 ===
 +
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 +
        // プレイヤーが「/basic」コマンドを投入した際の処理...
 +
if(cmd.getName().equalsIgnoreCase("basic")){
 +
// 何かの処理
 +
return true;
 +
                // コマンドが実行された場合は、trueを返して当メソッドを抜ける。
 +
}
 +
return false;
 +
        // コマンドが実行されなかった場合は、falseを返して当メソッドを抜ける。
 +
}</source></blockquote>
 +
onCommand()をコーディングする際は、上記の例のように、メソッドの最終ステップにfalseをreturnする行を記述するのが良い方法です。<BR/>
 +
falseが返る事で、plugin.yml(下記参照)内に記述されたメッセージが表示され、<BR/>
 +
onCommand()処理が正しく動作しなかった事をメッセージから検知できるため、動作確認の助けになるからです。<BR/>
 +
逆に、メソッドの最後でtrueを返す処理構造にしてしまった場合は、onCommand()内の各処理に対して、<BR/>
 +
処理結果チェック処理と結果が不正である場合にfalseを返すような、同じ処理を何度も記述する必要が出てきますので、大変な無駄となります。<BR/>
 +
<BR/>
 +
なお、コード「.equalsIgnoreCase("basic")」のパラメタ「basic」は、大文字小文字の区別はありません。
 +
 +
=== plugin.ymlへのコマンドの追加 ===
 +
 +
コマンドを用意する際は、plugin.ymlファイルにも定義を追加する必要があります。plugin.ymlの最後に次の行を追加します。
 +
<blockquote><source lang="yaml">
 +
commands:
 +
  basic:
 +
      description: This is a demo command.
 +
      usage: /<command> [player]
 +
      permission: tutorialplugin.basic
 +
      permission-message: You don't have <permission>
 +
</source>
 +
* basic - コマンド名。
 +
* description - コマンドの説明文。
 +
* usage - コマンドの使い方。onCommand() でfalseを返したときに、コマンド実行ユーザに向けて表示されるメッセージの内容。あなたの作ろうとしているコマンドの使い方を、わかりやすく説明してください。
 +
* permission - コマンドの実行権限。このコマンドの実行に必要なパーミッションノードを設定します。あなたのプラグイン名と、コマンド名を、ピリオド(.)でつなげたパーミッションノードを設定することを推奨します(例:myplugin.test)。
 +
* permission-message - 上で設定したコマンド実行権限を持たないユーザがコマンドを実行した場合に、実行権限が無いことを同ユーザに知らせるメッセージ。
 +
</blockquote>
 +
 +
なお、usage の欄では <command>、permission-message の欄では <permission> というキーワードをそのまま指定できます。それぞれ、設定したコマンド名と、設定したパーミッションノードに置き換えられます。<br>
 +
詳しい書き方は、[[plugin.ymlの設定一覧#コマンドのオプション設定|plugin.ymlの書き方の、コマンドのオプション設定の節]] を参照してください。
 +
 +
:'''Note''': ymlファイルには、インデントに2個以上の半角スペースを記述する必要があります。タブ文字は構文エラーとなるため利用できません。
 +
 +
=== コンソールコマンドとプレイヤーコマンド ===
 +
 +
察しの良い人は、<code>'''CommandSender sender'''</code> パラメタに着目しているかもしれません。 <code>'''CommandSender'''</code>クラスは、プラグイン開発者に2つの点で有用な、Bukkitが提供するインタフェースです。
 +
 +
CommandSenderインタフェースの実装: <code>'''Player'''</code> と <code>'''ConsoleCommandSender'''</code> (正確には <code>'''Player'''</code>もインタフェース)です。
 +
プラグインを作る際は、次の2点に注意しながら作る事が非常に良い方法です。
 +
* プラグインが動作している事をサーバコンソールで確認しながら作る事
 +
* ログインプレイヤー向けのコマンドが、実際にログインしているプレイヤーのみ実行できる事
 +
プラグインは、senderがプレイヤーではない場合(例えばコンソールからプラグインのコマンドが投入された場合)であっても、サーバコンソールには明解でシンプルな動作結果を返します。(例えば天気を変えるコマンドの実行結果は、ゲーム内では無くサーバコンソール上のメッセージから確認すれば間違いありません)
 +
 +
記述例:
 +
<blockquote><source lang="java">
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
if (cmd.getName().equalsIgnoreCase("basic")) {
 +
// プレイヤーが /basic を実行すると、この部分の処理が実行されます...
 +
return true;
 +
} else if (cmd.getName().equalsIgnoreCase("basic2")) {
 +
                // プレイヤーが /basic2 を実行すると、この部分の処理が実行されます...
 +
if (!(sender instanceof Player)) {
 +
sender.sendMessage("このコマンドはゲーム内から実行してください。");
 +
} else {
 +
Player player = (Player) sender;
 +
// コマンドの実行処理...
 +
}
 +
return true;
 +
}
 +
return false;
 +
}
 +
</source> </blockquote> 
 +
 +
この例では、'''basic''' コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、'''basic2''' コマンドは、ログインプレイヤーしか実行できません。
 +
一般的に、コマンドはプレイヤーとサーバコンソールの両者に実行を許可しますが、投入されたコマンドの実行可否をチェックする必要があるコマンドについては、ログインプレイヤー向けのコマンドとして実装されるべきでしょう。
 +
 +
つまり、プレイヤーに依存する処理(テレポートコマンドやアイテムを与えるコマンド等は、対象となるプレイヤーが必要です)を行うコマンドが、プレイヤーコマンドとして実装されるべきものと言えます。
 +
 +
凝ったコマンドを作りたい場合、コマンドのパラメタ(上記例では <code>'''args'''</code>パラメタ)を利用して独自の処理実装を行うことができます。例えば、プレイヤー名を指定したテレポートコマンドのみが、サーバコンソールから実行できる実装にする等が考えられます。
 +
 +
:'''訳者補記''': コマンド実装方法には、プレイヤー向け・サーバコンソール向けの2種類があり、それぞれの区別はどのような考え方で行うべきかについて説明しています。
 +
 +
=== CommandExecutorのクラス分割 ===
 +
 +
上記の例では、onCommand()メソッドをプラグインのメインクラスに記述していました。小さなプラグインでは良い方法ですが、大きなプラグインに拡張していくのであれば、適切なクラスを作成してそちらに配置するべきです。幸い、難しい事ではありません:
 +
 +
* プラグインのpackage内に、'''MyPluginCommandExecutor''' のような名前で新規のコマンド実行クラスを作成する(当然、MyPluginはあなたのプラグイン名に合わせて変えましょう)。
 +
* MyPluginCommandExecutorに、BukkitのCommandExecutorインタフェースを実装(implements)させる。
 +
* プラグインのonEnable()メソッドの処理で、MyPluginCommandExecutorクラスのインスタンスを生成する。
 +
* MyPluginCommandExecutorインスタンスをパラメタとして、処理<code>'''getCommand("basic").setExecutor(myExecutor);'''</code>を実行させる。
 +
 +
:'''Note''': <code>'''"basic"'''</code>は実行されたコマンドであり、<code>'''myExecutor'''</code>はMyPluginCommandExecutorインスタンスです。
 +
とっても良い具体例:
 +
 +
MyPlugin.java (プラグインのメインクラス):
 +
<blockquote><source lang="java">
 +
private static Plugin instance;
 +
 +
@Override
 +
public void onEnable() {
 +
instance = this;
 +
// ...
 +
 +
// plugin.yml に basic というコマンドを定義していないと、
 +
        //実行した時にNullPointerExceptionが発生します。注意してください。
 +
getCommand("basic").setExecutor(new MyPluginCommandExecutor());
 +
 +
// ...
 +
}
 +
 +
public static Plugin getInstance() {
 +
return instance;
 +
}
 +
</source></blockquote>
 +
 +
MyPluginCommandExecutor.java:
 +
<blockquote><source lang="java">
 +
public class MyPluginCommandExecutor implements CommandExecutor {
 +
private Plugin instance = MyPlugin.getInstance();
 +
        // メインクラスの参照です。処理の中でメインクラスのメソッドを利用しない場合は、省略して構いません。
 +
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
// コマンドの実行内容...
 +
}
 +
}
 +
</source></blockquote>
 +
どのように、メインプラグインのインスタンスが'''MyPluginCommandExecutor'''のコンストラクタを実行するかに注目しましょう。
 +
 +
この節で紹介した方法により、メインのonCommand()メソッドが巨大で複雑になったとしても簡単に整理する事ができ、結果として、プラグインのメインクラスを複雑化させずに、処理分割する事ができます。
 +
 +
:'''Note''': プラグインが複数のコマンドを持つ場合、個々のコマンドに対応する'''CommandExecutor'''をコーディングする必要があります。
 +
 +
:'''訳者補記''': 積極的に、大きな処理は小さく分割していきましょう。そのための仕組みをBukkitが用意しています。という事を教示しています。
 +
 +
== 堅牢なonCommandの記述 ==
 +
onCommand()メソッドを記述する際、パラメタの利用に関して、<br/>
 +
決め付けて掛かってはいけない事がありますので念頭において下さい。<br/>
 +
これらに留意した処理を記述する事で、堅牢なonCommand()をコーディングする事ができます。
 +
 +
=== senderの内部データ型をチェックする ===
 +
senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。<br/>
 +
処理例:
 +
<blockquote><source lang="java">
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 +
    if ((sender instanceof Player)) {
 +
        Player player = (Player) sender;
 +
        // doSomething
 +
    } else {
 +
        sender.sendMessage(ChatColor.RED + "ゲーム内から実行してください!");
 +
        return false;
 +
    }
 +
    return false;
 +
}
 +
</source> </blockquote>
 +
 +
=== コマンドのパラメタ長をチェックする ===
 +
senderインスタンスは、妥当なパラメタを持っているとは限りません。パラメタ長をチェックして下さい。<br/>
 +
処理例:
 +
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 +
if (args.length > 4) {
 +
          sender.sendMessage(ChatColor.RED + "パラメタが多すぎます!");
 +
          return false;
 +
        }
 +
        if (args.length < 2) {
 +
          sender.sendMessage(ChatColor.RED + "パラメタが足りません!");
 +
          return false;
 +
        }
 +
}</source> </blockquote>
 +
 +
=== プレイヤーがオンラインである事を確認する ===
 +
特定のプレイヤーのPlayerインスタンスを利用したい場合、<br/>
 +
必ずそのプレイヤーがオンラインである必要があります。<br/>
 +
オンラインであるかどうかをチェックして下さい。<br/>
 +
処理例:
 +
 +
<!-- ucchy 2014.05.13記 ここのサンプル実装は、CraftBukkit 1.7.9-R0.1 で一旦depricatedされた内容を避けて書いるため、原文のものと異なります。再翻訳時には原文を参照して更新してください。 -->
 +
<blockquote><source lang="java">
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 +
    Player other = getPlayer(args[0]);
 +
    if (other == null) {
 +
        sender.sendMessage(ChatColor.RED + args[0] + "さんはオフラインです!");
 +
        return false;
 +
    }
 +
    return false;
 +
}
 +
 +
private Player getPlayer(String name) {
 +
    for ( Player player : Bukkit.getOnlinePlayers() ) {
 +
        if ( player.getName().equals(name) ) {
 +
            return player;
 +
        }
 +
    }
 +
    return null;
 +
}
 +
</source> </blockquote>
 +
 +
オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的には<code>'''OfflinePlayer'''</code>クラスを利用して行う事ができます。
 +
 +
== プラグインのConfiguration/Settings ==
 +
:: ''[[新しいConfigurationの使い方]]を参照して下さい ''
 +
 +
== 権限 ==
 +
Bukkitのパーミッション(権限)APIの利用は、簡単ではありません。
 +
 +
プレイヤーが特定の権限を持っているかどうかを調べる処理は次のようになります:
 +
<blockquote><source lang="java">if(player.hasPermission("some.pointless.permission")) {
 +
  //Do something
 +
}else{
 +
  //Do something else
 +
}</source></blockquote>
 +
 +
権限がセットされているか、いない(Javaの'''null'''と同等)かを調べる処理は次のようになります:
 +
<blockquote><source lang="java">boolean isPermissionSet(String name)</source></blockquote>
 +
 +
なぜグループの概念が存在しないかと思った方もいるでしょうが、
 +
そもそも、権限にはグルーピングの概念は不要なのです。
 +
 +
元々、グループの主な用途はチャットメッセージをフォーマッティングする事にありました。<br/>
 +
これは権限の機能を利用してもっと簡単に行えます。<br/>
 +
例えば、チャットプラグインの設定において、権限とプレフィクスの関連を定義する事が該当します。<br/>
 +
具体的には、権限'''"someChat.prefix.admin"'''をプレフィクス'''[Admin]'''に対応させる定義を行い、<br/>
 +
プレイヤーがチャットで発言する度に、プレイヤー名の先頭に'''[Admin]'''が付くようになる機能が挙げられます。
 +
 +
他にも、グループに所属する複数のユーザに、<br/>
 +
メッセージを送信するような機能を実現するために利用する事が考えられます。<br/>
 +
この例を、権限を利用した処理で記述すると以下のようになります:
 +
<blockquote><source lang="java">for(Player player: getServer().getOnlinePlayers()) {
 +
 +
    if(player.hasPermission("send.recieve.message")) {
 +
        player.sendMessage("You were sent a message");
 +
    }
 +
 +
}</source> </blockquote>
 +
 +
さて、依然として<br/>
 +
「グループを利用せずに、複数のプレイヤーに権限をセットする良い方法は何なのか?」が疑問かと思いますが・・・<br/>
 +
BukkitのAPI自体は、グループの概念を提供していません。<br/>
 +
グループの概念を利用するためには、permissionsBukkitのような<br/>
 +
グループ権限の機能を提供するプラグインを利用する事になります。<br/>
 +
とどのつまり、'''このAPIはインタフェース(Interface)であり、実装(Implementation)ではない'''のです。
 +
 +
=== 権限の設定 ===
 +
 +
権限を利用して細かな制御を行いたい場合は、<br/>
 +
デフォルト権限と、子権限の設定を''plugin.yml''に追記する事を検討して下さい。<br/>
 +
この2種類の設定は、オプション(必須ではなく完全に任意で利用される)項目ではありますが、お勧めします。<br/>
 +
 +
下記は、''plugin.yml''の最後に追加する形で設定する権限です:
 +
<blockquote><code><source lang="yaml">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</source>
 +
</code></blockquote>
 +
 +
 +
まず、プラグインが利用する各権限を、''permissions''ノードの子ノードとして定義します。<br/>
 +
それぞれの権限はオプションとして、概要(description)、デフォルト値、子ノードを持ちます。
 +
 +
==== デフォルト権限 ====
 +
 +
プレイヤーが権限を持たない場合、&nbsp;''hasPermission''&nbsp;はデフォルトではfalseを返します。<br/>
 +
''plugin.yml''中のデフォルトのノードに対して、下記の4つのどれか1つを設定する事で、<br/>
 +
この挙動を変更する事ができます:
 +
*'''true''' - デフォルトで、権限を持つ(=hasPermissionがtrueを返す)。
 +
*'''false''' - デフォルトで、権限を持たない(=hasPermissionがfalseを返す)。
 +
*'''op''' - プレイヤーがOPであれば、権限を持つ(=hasPermissionがtrueを返す)。
 +
*'''not op''' - プレイヤーがOPでなければ、権限を持つ(=hasPermissionがtrueを返す)。
 +
 +
==== 子権限 ====
 +
 +
恐らく今までに、''* 権限''を利用してサブ権限を割り当てた事があると思います。<br/>
 +
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、<br/>
 +
高い柔軟性を提供しています。<br/>
 +
下記はその実装例です:
 +
<blockquote><source lang="yaml">permissions:
 +
    doorman.*:
 +
        description: Gives access to all doorman commands
 +
        children:
 +
            doorman.kick: true
 +
            doorman.ban: true
 +
            doorman.knock: true
 +
            doorman.denied: false</source>
 +
 +
''doorman.*''権限は、いくつかの子権限を含んでいます。
 +
''doorman.*''権限がtrueである場合に、
 +
その子権限は''plugin.yml''に定義された値(trueかfalse)をデフォルト権限として機能します。
 +
''doorman.*''権限がfalseである場合は、
 +
その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。
 +
</blockquote>
 +
 +
=== 独自の権限設定 ===
 +
 +
独自に権限の仕組みを提供するプラグインを開発したい場合、&nbsp;[[権限プラグインの開発方法]]を参考にして下さい。
 +
 +
== スケジューリングタスクとバックグラウンドタスク ==
 +
 +
現在、Minecraftサーバはゲームロジックのほとんどがシングルスレッドで稼動しています。<br/>
 +
このため、発生する個々の処理はごく小さいものにする必要があります。<br/>
 +
プラグイン中に複雑なコードが存在して、それが適切に処理されないような場合は、<br/>
 +
ゲームロジックに多大なラグや処理遅延を発生させる原因となります。<br/>
 +
 +
幸運なことに、Bukkitはプラグインに対してスケジューリングのためのコーディング方法を提供しています。<br/>
 +
どこかのタイミングで一度だけRunnableタスクを実行したり、<br/>
 +
小さなタスクに分けた定期的な繰り返しであったり、<br/>
 +
新規に独立したスレッドを派生させたり・・・<br/>
 +
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。
 +
 +
詳しくは、[[スケジューラのプログラミング]]中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。
 +
 +
== ブロックの操作 ==
 +
 +
ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。<br/>
 +
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、<br/>
 +
まずはそのブロックを取得した上で、変更を加えることになります。<br/>
 +
PlayerMoveイベントの処理中でこれを行う例を示します:
 +
<blockquote><source lang="java">public void onPlayerMove(PlayerMoveEvent evt) {
 +
    // プレイヤーの位置を取得します。
 +
    Location loc = event.getPlayer().getLocation();
 +
    // 位置のY座標を+5します。位置情報を変更しているだけで、実際にプレイヤーの位置が移動するわけではないことに注意してください。
 +
    loc.setY(loc.getY() + 5);
 +
    // 指定位置のブロックを取得します。
 +
    Block b = loc.getBlock();
 +
    // ブロックの種類に石(STONE)を設定します。
 +
    b.setType(Material.STONE);
 +
}</source></blockquote>
 +
 +
このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、<br/>
 +
プレイヤーの5ブロック上方のブロックがStoneに変化する動作として見えるでしょう。<br/>
 +
ブロック取得までの流れは・・・
 +
# 取得したプレイヤーの位置から、ワールドを取得する
 +
# 位置のY座標を+5する
 +
# <code>'''loc.getBlockAt(loc);'''</code>で、指定位置に存在するブロックを得る
 +
となります。<br/>
 +
 +
位置(''loc'')中のブロックインスタンス(''b'')に対して、<br/>
 +
Materialを変えたり、ブロックデータ自体を変更する事もできます。
 +
詳しくは、JavaDocを参照してください。<br/>
 +
 +
建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う一例を示します。<br/>
 +
例えば、固体ブロックによる立方体を生成する処理は、<br/>
 +
3階層にネストしたforループ処理によって記述できます。
 +
<blockquote><source lang="java">
 +
    public void generateCube(Location loc, int length){
 +
        // 与えられたLocationから、立方体の端の座標を取得します。
 +
        // getN()メソッドを使うと intへキャストする必要がありますが、
 +
        // getBlockN()メソッドを使えばそのままintで座標を取得できます。
 +
        int x1 = loc.getBlockX();
 +
        int y1 = loc.getBlockY();
 +
        int z1 = loc.getBlockZ();
 +
   
 +
        // 一辺の長さを足すことで、立方体の反対側の座標を計算します。
 +
        int x2 = x1 + length;
 +
        int y2 = y1 + length;
 +
        int z2 = z1 + length;
 +
   
 +
        World world = loc.getWorld();
 +
   
 +
        // x座標方向のループ
 +
        for (int xPoint = x1; xPoint <= x2; xPoint++) {
 +
            // y座標方向のループ
 +
            for (int yPoint = y1; yPoint <= y2; yPoint++) {
 +
                // z座標方向のループ
 +
                for (int zPoint = z1; zPoint <= z2; zPoint++) {
 +
                    // ループで処理する座標のブロックを取得します。
 +
                    Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
 +
                    // ダイアモンドブロックに設定します!
 +
                    currentBlock.setType(Material.DIAMOND_BLOCK);
 +
                }
 +
            }
 +
        }
 +
    }
 +
</source></blockquote>
 +
 +
このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。<br/>
 +
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。<br/>
 +
ただし、セットするブロックの種類はMaterial.AIRになりますね。
 +
 +
== プレイヤーインベントリの操作 ==
 +
 +
この節では、プレイヤーのインベントリ操作を特に扱っていますが、<br/>
 +
チェストのインベントリの操作にも適用できますので、必要なら応用して下さい(゚∀゚)<br/>
 +
インベントリ操作のシンプルな例:
 +
<blockquote><source lang="java">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 + "よく来たな!もっとダイヤモンドをくれてやろう、このとんでもない成金め!!");
 +
    }
 +
}</source> </blockquote>
 +
 +
さて、onPlayerJoin()メソッドの先頭で<br/>
 +
'''player''', '''inventory'''、'''diamondstack'''変数を用意して下さい。<br/>
 +
'''inventory'''はプレイヤーのインベントリで、'''diamondstack'''は(アイテムとしての)64個のダイヤモンドです。
 +
 +
次に、プレイヤーのインベントリがダイヤモンドを含んでいるかをチェックします。<br/>
 +
プレイヤーがダイヤモンドをインベントリに所持している場合、<br/>
 +
<code>'''inventory.addItem(diamondstack)'''</code>処理にて<br/>
 +
同プレイヤーのインベントリに別のスタックを与え、黄金色のメッセージを送信します。<br/>
 +
インベントリ操作は全然難しくありません。
 +
 +
ダイヤモンドのスタックを削除したい場合でも単純に、<br/>
 +
<code>'''inventory.addItem(diamondstack)'''</code>を<br/><code>'''inventory.remove(diamondstack)'''</code>に置き換えるだけです。<br/>
 +
(メッセージにも少しイタズラしておきましょう)
 +
 +
== アイテムの操作 ==
 +
アイテムはスタックという単位で操作します。<br/>
 +
スタックデータに対する全ての操作は、[http://jd.bukkit.org/apidocs/org/bukkit/inventory/ItemStack.html ItemStackクラスの仕様]を参照して下さい。
 +
 +
=== エンチャント ===
 +
 +
<!-- ucchy 2014.05.13記 エンチャントIDと、EnchantmentWrapperを利用する方法は、現在では非推奨ですが、原文もまだ古いままです。訳文だけ先行して修正しますが、原文が更新されたら修正してください。 -->
 +
 +
<!--
 +
アイテムに対するエンチャントに触れる前に、[http://ja.minecraftwiki.net/wiki/Data_values Item Code] と [http://ja.minecraftwiki.net/wiki/Enchanting EID]を見てから以下の解説を読んでください。
 +
 +
エンチャントは、Enchantmentクラスが受け持っている機能ですが、<br/>
 +
Enchantmentクラス自体は抽象クラスであるため、インスタンス化('''new Enchantment()''')出来ません。
 +
エンチャントはEnchantmentWrapperクラスから利用する必要があるからです。
 +
-->
 +
 +
アイテムにエンチャントを付与するには、ItemStackクラスの '''addEnchantment(Enchantment enchant, int level)''' メソッドを使用します。
 +
 +
addEnchantment()メソッドでは、元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。<br/>
 +
もし、通常ありえないエンチャントを設定したい場合は、addEnchantment()メソッドの代わりにaddUnsafeEnchantment()メソッドを使ってください。
 +
 +
<blockquote><source lang="java">
 +
// 木の棒のインスタンスを生成する
 +
ItemStack myItem = new ItemStack(Material.STICK);
 +
 +
// 木の棒にFireAspectレベル100を付与する
 +
//(ただしFireAspectレベル100は通常存在しないので、付与は成功しない)
 +
myItem.addEnchantment(Enchantment.FIRE_ASPECT, 100); 
 +
</source></blockquote>
 +
 +
== Metadata ==
 +
 +
Bukkit では、プラグインの開発をより簡単にするため、Playerクラス、Entityクラス、Worldクラスに紐づく追加データをMetadataという形式で管理できるようになっています。
 +
今までは、それぞれのプラグイン内で、Player、Entity、World などをキーとしたHashMap型の変数内で管理していたと思いますが、それをMetadataで置き換えすることができます。
 +
Metadataのデータは、全てMetadatableのメンバーで構成されます([http://jd.bukkit.org/doxygen/de/d59/interfaceorg_1_1bukkit_1_1metadata_1_1MetadataValue.html#ab49975fe013a0626dd29d3b85c63a82f javadoc]も参照してください)。
 +
動作は非常に単純です。
 +
Metadatableクラスは、それぞれのインスタンスが自分のMetadataのHashMapを持っています。
 +
つまり例えば、経済プラグインを作る場合、HashMap<Player, Double> のようなデータをプラグイン内で持つ必要はありません。
 +
プレイヤーにMetadataを直接設定すればよいのです!
 +
 +
=== Metadataを使うメリット ===
 +
 +
* Metadataは全てBukkit側で管理されます。プラグイン側で管理する必要がありません。
 +
* プラグイン間で共通にアクセスできるので、データの共有に使用できます。
 +
 +
=== Metadataを使うデメリット ===
 +
 +
* データの取得・設定を行うときに、ひと手間が必要になります。
 +
* Bukkitが停止すると、全てのMetadataが消えます。
 +
 +
=== Metadataの使い方 ===
 +
 +
<source lang="java">
 +
 +
    /**
 +
    * 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;
 +
    }
 +
</source>
 +
 +
:'''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のプラグインで利用するためのライブラリを使う方法のチュートリアル、[http://forums.bukkit.org/threads/lib-tut-mysql-sqlite-bukkit-drivers.33849/ fantastic SQLite tutorial]を書きました。
 +
 +
SQLの構文については、次に紹介する動画を一度見てみる事をお勧めします。簡単な内容なので短時間で済みます。[http://www.w3schools.com/sql/default.asp SQL Tutorials @W3Schools]
 +
 +
SQLiteは、その稼動のためにサーバを構築する必要がないため、シンプルさにおいて非常に優れています。
 +
新規にデータベースとその中のテーブルを作成する手順は少量です。また、データのバックアップも、1個のデータベースファイルをバックアップするだけです。ただし、データ間の整合性の確保や柔軟性、データ件数が100万を超えるような膨大なデータの扱いにおいては、多少弱い面もあります。
 +
 +
とはいえSQLiteは、SQLデータベースを利用する新規プラグインの開発作業を、迅速で簡単にしてくれるメリットがあります。また、サーバ向けの大規模なSQLの予習のためにも有用です。
 +
 +
=== MySQL  ===
 +
もうひとつ、人気があるSQLにMySQLというものがあります。
 +
SQLiteよりもサーバ寄りの用途をもった種類のSQLで、多くの有名な企業の業務システムや、一日に100万アクセスを捌くようなウェブサイトが、この機能に依存しています。ただし、チューニング可能な範囲や機能が多岐に渡るため、セキュリティ上のリスクが、データベース管理者のMySQLへの習熟度に大きく左右されます。
 +
 +
プラグインからMySQLを利用する処理のコーディング自体は、小規模なSQLiteからMByte単位のOracleのデータベースに対するものと、大差ありません。しかし、サーバ用途のデータベースの管理作業は、どんどん増えていくものです。使い続ける内に、データベース利用者のアカウントや権限の設定作業を行う事になっていくでしょう。また、データのバックアップやバックアップからの復旧(Rollbackと呼ぶ)作業のために、SQL文のスクリプトを書く事にもなるはずです。
 +
 +
== プラグインの配布 ==
 +
 +
プラグインを実装し終わったら、Mavenを使ってビルドして、リリース用のJarファイルを作成してみましょう。
 +
 +
プロジェクトを右クリックして、実行>Maven install と選択してください。
 +
 +
[[Image:Plugin_deploy1.png]]
 +
 +
Eclipseのコンソールに、ビルド情報が流れます。<br/>
 +
ビルドがうまくいけば、コンソールに「BUILD SUCCESS」と表示されてビルドが終了します。<br/>
 +
もしビルドが失敗したなら、エラー情報を元に、エラーの解決を試みてください。
 +
 +
このチュートリアルで紹介したJDKが同梱のPleiadesを利用していない場合、MavenがJDKを見つけられずにエラーになっていることが多いです。<br/>
 +
その場合は、Eclipseの設定を開いて、正しいJDKを選択しなおしてください。<br/>
 +
Eclipseの設定は、「ウィンドウ」メニュー>設定 を選択し、開いたダイアログで、Java>インストール済みのJRE を選択します。<br/>
 +
ここで、正しいバージョンのJDKが選択されていることを確認してください。
 +
 +
 +
ビルドがうまくいった場合、プロジェクトのフォルダの中に target フォルダが作成されており、そのフォルダの中にビルドされたJarファイルがあります。
 +
 +
[[Image:Plugin_deploy2.png]]
 +
 +
 +
プラグインのコードとplugin.ymlに不備が無ければ、エクスポートしたJarファイルはすぐにBukkitプラグインとして動作します。Jarファイルを、Bukkitサーバの'''"plugins"'''フォルダの中に配置し、Bukkitサーバを起動し、プラグインの動作確認をしてみましょう。なお、ローカルマシン上で起動するBukkitサーバへは、Minecraftクライアントのマルチプレイヤーサーバの接続先IPアドレスに'''"localhost"'''を指定して接続する事でログインできます。
 +
 +
もしプラグインが上手く動かず、それがどうしても自分で解決できない場合は、当Wikiやその原文をもう一度よく読み、それでも駄目なら[http://forums.bukkit.org/forums/plugin-development.5/ Bukkitプラグイン開発者フォーラム(英語圏)], [http://wiki.bukkit.org/IRC bukkitdev Bukkit公式サイトの開発者向けIRCチャンネル(英語圏)], [http://forum.minecraftuser.jp/viewforum.php?f=21 マインクラフト非公式日本ユーザフォーラムの関連トピック(日本語圏)], [http://minecraftjp.info/modding/index.php/Minecraft_Modding_Wiki#IRC 当WikiのMOD制作関連IRCチャンネル(日本語圏)]をたずねてみて下さい。有用なプラグインが作れたら、[http://dev.bukkit.org/ dev.bukkit]に登録し、プラグインを広く公開する事を検討してみて下さい。他のBukkitユーザ・開発者に貢献する事ができます。
 +
 +
== ヒントとノウハウ ==
 +
CraftBukkit APIは、すばらしい機能をたくさん提供しています。
 +
下記に、面白い効果を実現するコードを示します。
 +
 +
=== プレイヤーに着火する ===
 +
下記は、指定されたプレイヤーに着火するサンプルです。例えば '''/ignite Notch''' と実行すると、Notchが燃えます!
 +
<blockquote><source lang="java">
 +
@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 = getPlayer(args[0]);
 +
 +
        // 対象プレイヤーが、オンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーが見つかりません!");
 +
            return true;
 +
        }
 +
 +
        // 対象プレイヤーを、1000tick(=50秒) の間、燃えるようにします。
 +
        target.setFireTicks(1000);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
 +
private Player getPlayer(String name) {
 +
    for ( Player player : Bukkit.getOnlinePlayers() ) {
 +
        if ( player.getName().equals(name) ) {
 +
            return player;
 +
        }
 +
    }
 +
    return null;
 +
}
 +
</source> </blockquote>
 +
 +
=== プレイヤーを殺す ===
 +
 +
同じ要領で、プレイヤーを殺害するコマンドの例を紹介します。
 +
onCommand()メソッドに記述します:
 +
<blockquote><source lang="java">
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
 +
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
 +
        Player target = getPlayer(args[0]);
 +
        // 対象プレイヤーがオンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        // 対象に1000ダメージを与える
 +
        target.damage(1000);
 +
    }
 +
    return false;
 +
}
 +
 +
private Player getPlayer(String name) {
 +
    for ( Player player : Bukkit.getOnlinePlayers() ) {
 +
        if ( player.getName().equals(name) ) {
 +
            return player;
 +
        }
 +
    }
 +
    return null;
 +
}
 +
</source></blockquote>
 +
 +
上記の拡張版として、プレイヤーを爆死させる処理を下記に示します:
 +
<blockquote><source lang="java">
 +
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.damage(1000);
 +
</source> </blockquote>
 +
 +
=== 爆発を起こす ===
 +
このコードは、TNTの爆発と同様の音とヴィジュアルを再現します。
 +
これは、TNTの爆発効果は無効化しつつ、音とヴィジュアル効果を発生させる処理に転用できます。
 +
<blockquote><source lang="java">
 +
@EventHandler
 +
public void onExplosionPrime(ExplosionPrimeEvent event) {
 +
    Entity entity = event.getEntity();
 +
 +
    // このイベントは、点火されたTNTにより発生したのかどうかを確認します。
 +
    // (つまり、TNTの爆発はこれで無効化されますが、クリーパーの爆発は無効化されません)
 +
    if (entity instanceof TNTPrimed) {
 +
        event.setCancelled(true); // イベントをキャンセルして、爆発を無かったことにする
 +
        entity.getWorld().createExplosion(entity.getLocation(), 0); // 偽物の爆発を発生させる
 +
    }
 +
}
 +
</source></blockquote>
 +
 +
=== プレイヤーを非表示にする ===
 +
 +
これは指定したプレイヤーから自分を非表示にするサンプルです。
 +
指定したプレイヤー以外のプレイヤーからは、自分が見えたままになっています。
 +
 +
<blockquote>
 +
<source lang="java">
 +
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 = getPlayer(args[0]);
 +
        if (target == null) {
 +
            sender.sendMessage("Player " + args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。
 +
        target.hidePlayer(s);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
 +
private Player getPlayer(String name) {
 +
    for ( Player player : Bukkit.getOnlinePlayers() ) {
 +
        if ( player.getName().equals(name) ) {
 +
            return player;
 +
        }
 +
    }
 +
    return null;
 +
}
 +
</source>
 +
</blockquote>
 +
 +
=== クリックした場所に雷を落とす ===
 +
 +
下記のサンプルは、釣竿を持ってクリックしたときに、クリックした場所を取得して、その場所に雷を落とします。
 +
 +
<blockquote>
 +
<source lang="java">
 +
@EventHandler
 +
public void onPlayerInteractBlock(PlayerInteractEvent event) {
 +
 +
    Player player = event.getPlayer();
 +
 +
    if (player.getItemInHand().getType() == Material.FISHING_ROD) {
 +
        // プレイヤーが見ている場所に雷をおとします。
 +
 +
        Block target = getTargetBlock(player);
 +
        if (target != null) {
 +
            target.getWorld().strikeLightning(target.getLocation());
 +
        }
 +
    }
 +
}
 +
 +
private Block getTargetBlock(Player player) {
 +
   
 +
    // 視線上のブロックを100ブロック先まで取得
 +
    BlockIterator it = new BlockIterator(player, 100);
 +
 +
    // 手前側から検証を行う。
 +
    // Blockが取得できた時点でreturnして終了する。
 +
    while ( it.hasNext() ) {
 +
 +
        Block block = it.next();
 +
 +
        if ( block.getType() != Material.AIR ) {
 +
            // ブロックが見つかった
 +
            return block;
 +
        }
 +
    }
 +
 +
    // 最後までブロックがみつからなかった
 +
    return null;
 +
}
 +
</source>
 +
</blockquote>
 +
 +
== リクエストに応じて書かれた記事 ==
 +
 +
=== mavenを利用したプラグイン開発 ===
 +
gitのリポジトリ上にある'''BukkitPluginArchetype'''をcloneし、それをビルドする:
 +
<blockquote><source lang="bash">
 +
git clone git://github.com/keyz182/BukkitPluginArchetype.git
 +
cd BukkitPluginArchetype
 +
mvn clean install
 +
</source></blockquote>
 +
 +
作成するプラグインのフォルダに移動して下記のコマンドを実行します:
 +
<blockquote><source lang="bash">mvn archetype:generate -DarchetypeCatalog=local</source></blockquote>
 +
 +
プロンプトに表示されたリストから下記を選択します:
 +
<blockquote>uk.co.dbyz:bukkitplugin (bukkitplugin) </blockquote>
 +
 +
 +
GroupIDには筆頭(プラグインのトップ階層)としたいJavaパッケージ名を指定し、ArtifactIDにはパッケージ名の末端の名称(Jarファイルのような成果物の名称として利用されます)を入力します。そして、確認メッセージに'''Y&lt;enter&gt;'''で応答します。
 +
 +
例:
 +
<blockquote><source lang="bash">
 +
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:
 +
</source></blockquote>
 +
 +
ArchetypeIDに指定した文字列と同名のフォルダが生成され、配下に'''src'''フォルダと'''pom.xml'''ファイルが配置されます。
 +
 +
'''src/main/java/&lt;package&gt;'''フォルダに存在する'''&lt;archetypeid&gt;CommandExecuter.java'''ファイルを開き、コード<source lang="java">//Do Something</source>の部分に書きのコードを記述します:
 +
<blockquote><source lang="java">
 +
Player player = (Player) sender;
 +
player.setHealth(1000f);
 +
</source></blockquote>
 +
 +
ベースのフォルダに移動して下記を実行します:
 +
<blockquote><source lang="bash">
 +
mvn clean package
 +
</source></blockquote>
 +
ダウンロード処理が走りますが、他の作業を並行して行っても大丈夫です。
 +
''clean package''処理が完了すると、targetフォルダの中に'''&lt;archetypeid&gt;-1.0-SNAPSHOT.jar'''ファイルが生成されます(これがビルドされたプラグインのJarファイルです)。このファイルをBukkitのpluginsフォルダへコピーして、Bukkitサーバを起動して下さい。
 +
 +
ゲーム内で'''/&lt;archetypeid&gt;'''コマンドを実行すると、そのプレイヤーのHealthが全快します。
 +
 +
== プラグインのサンプル兼雛形 ==
 +
* [http://pastebin.com/fKjjReyv Example.Java]
 +
* [http://pastebin.com/VhNBnLPU ExamplePlayerListener.Java]
 +
* [http://pastebin.com/dRVeQKjw ExampleBlockListener.Java]
 +
* [http://pastebin.com/GG5R4vAn ExampleEntityListener.Java]
 +
 +
この内容について質問がある場合、遠慮なく[http://forums.bukkit.org/members/adamki11s.42417/ Adamki11s]か[http://wiki.bukkit.org/IRC BukkitDevのIRCチャンネル](当Wikiの原文を掲載しているサイトのIRCチャンネルです)で聞いてください。
 +
:'''Note''': 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。その点を考慮して必ず原文にも目を通してから質問して下さい。

2014年11月6日 (木) 19:11時点における版

本ページの内容は、Bukkit WikiPlugin Tutorialを和訳した物となります。(一部は省略しています)
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。

目次

始めに

重要: 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
重要: 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
重要: 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。当ページの内容を受けて原著者へ何らかのアクションを行う場合、その点を考慮して必ず原文にも目を通して下さい。

このチュートリアルは、Bukkitプラグインを開発するための基本的なノウハウを網羅しております。

Javaを習得する、IDEで開発環境を構築する、などの非常に基本的な部分から説明が始まっていますが、既にご理解頂いている場合は読み飛ばしていただいて構いません。

Javaの習得

Note: 当記事の一部は原文の訳ではありません、日本語読者向けに独自の内容を記述しています。

このチュートリアルの読者には、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を利用する場合、「開発対象用 JDK」と、ビルドツールMavenが実行できるプラグイン「m2e」が同梱されているものを選択してください。
(Eclipseを既に利用している場合でも、m2eを後から追加インストールすることは可能です。)
以下、m2eが既に導入されている前提で説明します。

Plugin用プロジェクトを始めるために

プロジェクトの作成

始めるために、新しいワークスペースを作成する必要があります。
Eclipseを起動し、ファイル>新規>プロジェクト... と選択して下さい。

Create project.png

新規プロジェクトのダイアログが開きます。Mavenフォルダを開いて、Mavenプロジェクト を選択し、次へ を押してください。

Create project1.png

次のダイアログで、「シンプルなプロジェクトの作成」にチェックを入れて、次へ を押してください。

Create project2.png

次のダイアログで、作成するプラグインの「グループ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 と名付けます。

ダイアログには次のように入力し、完了 を押してください。

Create project3.png

Eclipseの画面に戻ると、左側に TutorialPlugin プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?

Bukkit API の参照設定

次に、左側に作成された TutorialPlugin プロジェクトの中にある、「pom.xml」というファイルをダブルクリックしてください。そして、画面右下のあたりにある「pom.xml」というタブをクリックしてください。

Create project4.png

まず、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.7.9-R0.2</version>
   </dependency>
 </dependencies>

この設定では、Bukkit API 1.7.9-R0.2 が参照されます。
別のバージョンを参照したい場合は、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.7.9-R0.2</version>
      <type>jar</type>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

pom.xmlの編集が完了したら、Ctrl + S でファイルを保存してください。

次に、プロジェクトを右クリックして、Maven>プロジェクトの更新... と選択してください。

Create project7.png

Mavenプロジェクトの更新ダイアログが開きます。そのまま OK を押してください。

Create project5.png

Eclipseが、Bukkit API のダウンロードを開始します。
しばらく待つと、プロジェクトが更新され、「Maven依存関係」というところにダウンロードされたBukkit APIが表示されます。
また、JREシステム・ライブラリ のところも、指定したJavaビルドバージョンが反映されていることを確認して下さい。

Create project6.png

訳者補記: ダウンロードされた bukkit-x.x.x-Rxx.jar を右クリックし、Maven > Javadoc のダウンロード を実行しておくとよいでしょう。

パッケージの作成

次に、作成したプラグインのプロジェクトに、パッケージを追加します。
「src/main/java」のところを右クリックして、新規>パッケージ と選択してください。

Create package1.png

パッケージ名は、先ほど設定したグループIDに、プラグイン名を小文字に変換した名前を後ろに付けたものが望ましいです。
ここでは例として、パッケージIDに my.test.plugin、プラグイン名に TutorialPlugin を使っているので、my.test.plugin.tutorialplugin と設定します。

Create package2.png

メインクラスの作成

次に、プラグインのメインクラスを作成します。
メインクラスは、JavaPlugin を継承する必要があります。
(逆に、メインクラス以外のクラスは、直接的にも間接的にも、JavaPlugin を継承しないようにしてください。CraftBukkit 1.7.2-R0.3 以降では、プラグインが正しく動作しなくなります。)
メインクラスは、プラグイン名と同じ名前にすることが望ましいです。

先ほど作成したパッケージを右クリックして、新規>クラス と選択してください。

Create package3.png

名前の欄にクラス名を入力してください。
スーパークラスの欄に「org.bukkit.plugin.java.JavaPlugin」と入力してください。
(参照... ボタンを押して、開いたダイアログに「JavaPlugin」と入力して該当クラスを検索して選択しても構いません。)

完了 を押して、新規クラスが作成されたことを確認して下さい。ソースコードは次のようになっているはずです。

package my.test.plugin.tutorialplugin;

import org.bukkit.plugin.java.JavaPlugin;

public class TutorialPlugin extends JavaPlugin {

}
Attention.pngWarning: メインクラスは、コンストラクタを実行したり、新しいインスタンスを作成したりしないでください。

plugin.ymlの作成

メインクラスが作成できたら、次は plugin.yml ファイルを作成します。
plugin.yml ファイルは、プラグインとしてBukkitに読み込みされるときに、プラグインの設定を記述しておくファイルです。
必須のファイルですから、必ず作成してください。

プロジェクトの src/main/resources を右クリックして、新規>ファイル を選択してください。

Create pluginyml1.png

開いたダイアログの ファイル名 の欄で、「plugin.yml」と入力し、完了を押してください。

Create pluginyml2.png

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
File Lightbulb.pngNote: メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。


plugin.yml の詳細な内容一覧は、plugin.ymlの設定一覧 をご参照ください。

onEnable()メソッドとonDisable()メソッド

このメソッドは、プラグインが有効/無効になったときに、Bukkitから呼び出しされます。
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。
onEnable()は、Bukkitが起動するときに、プラグインが有効化されたときに呼び出されます。onDisable()は、Bukkitが停止するときに、プラグインが無効化されたときに呼び出されます。

onEnable()とonDisable()の基本

前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。

package my.test.plugin.tutorialplugin;
 
import org.bukkit.plugin.java.JavaPlugin;
 
public class TutorialPlugin extends JavaPlugin {
 
    @Override
    public void onEnable() {
        // TODO ここに、プラグインが有効化された時の処理を実装してください。
    }
 
    @Override
    public void onDisable() {
        // TODO ここに、プラグインが無効化された時の処理を実装してください。
    }
}


現時点では、このメソッドは何も行いません。

Loggerを利用したメッセージ出力

始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。
ログを出力するには、getLogger()メソッドを実行してLoggerを取得し、そのinfoメソッドを呼び出します。

getLogger().info("onEnableメソッドが呼び出されたよ!!");

onDisable()メソッドについても同等の記述を行います。
メインクラスに、次のように実装してみてください。

package my.test.plugin.tutorialplugin;
 
import org.bukkit.plugin.java.JavaPlugin;
 
public class TutorialPlugin extends JavaPlugin {
 
    @Override
    public void onEnable() {
        getLogger().info("onEnableメソッドが呼び出されたよ!!");
    }
 
    @Override
    public void onDisable() {
        getLogger().info("onDisableメソッドが呼び出されたよ!!");
    }
}

ここまで作ったところで、プラグインのJarファイルの発行 を行ってプラグインを作成し、実際にCraftBukkitのpluginsフォルダへ導入して、コンソールに設定したログメッセージが表示されることを確認してみてください。

Reloadを制御する

サーバーが終了処理をするときや、開始処理をするときに限らず、 サーバーが /reload コマンドにより、プラグインがdisableされenableされる動作について理解しておくことも重要です。
サーバーが開始したときについてだけ初期化処理を考慮することは危険です。
なぜなら、プレイヤーは既にオンライン状態で接続していますし、ワールドデータやチャンクは既にロードされていますし、他にもいろいろ想定していない違いがあります。

  • /reload が実行されると、プラグインとしては、onDisable() が実行されて、onEnable() が実行されます。
  • staticで保持していないデータは、全て失われます。
  • 上記に書いてあるように、プレイヤーやワールドやチャンクは既にロード済みの状態です。

イベントとリスナ

新しい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: tutorialplugin.basic
      permission-message: You don't have <permission>
  • basic - コマンド名。
  • description - コマンドの説明文。
  • usage - コマンドの使い方。onCommand() でfalseを返したときに、コマンド実行ユーザに向けて表示されるメッセージの内容。あなたの作ろうとしているコマンドの使い方を、わかりやすく説明してください。
  • permission - コマンドの実行権限。このコマンドの実行に必要なパーミッションノードを設定します。あなたのプラグイン名と、コマンド名を、ピリオド(.)でつなげたパーミッションノードを設定することを推奨します(例:myplugin.test)。
  • permission-message - 上で設定したコマンド実行権限を持たないユーザがコマンドを実行した場合に、実行権限が無いことを同ユーザに知らせるメッセージ。

なお、usage の欄では <command>、permission-message の欄では <permission> というキーワードをそのまま指定できます。それぞれ、設定したコマンド名と、設定したパーミッションノードに置き換えられます。
詳しい書き方は、plugin.ymlの書き方の、コマンドのオプション設定の節 を参照してください。

Note: ymlファイルには、インデントに2個以上の半角スペースを記述する必要があります。タブ文字は構文エラーとなるため利用できません。

コンソールコマンドとプレイヤーコマンド

察しの良い人は、CommandSender sender パラメタに着目しているかもしれません。 CommandSenderクラスは、プラグイン開発者に2つの点で有用な、Bukkitが提供するインタフェースです。

CommandSenderインタフェースの実装: PlayerConsoleCommandSender (正確には Playerもインタフェース)です。 プラグインを作る際は、次の2点に注意しながら作る事が非常に良い方法です。

  • プラグインが動作している事をサーバコンソールで確認しながら作る事
  • ログインプレイヤー向けのコマンドが、実際にログインしているプレイヤーのみ実行できる事

プラグインは、senderがプレイヤーではない場合(例えばコンソールからプラグインのコマンドが投入された場合)であっても、サーバコンソールには明解でシンプルな動作結果を返します。(例えば天気を変えるコマンドの実行結果は、ゲーム内では無くサーバコンソール上のメッセージから確認すれば間違いありません)

記述例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
	if (cmd.getName().equalsIgnoreCase("basic")) { 
		// プレイヤーが /basic を実行すると、この部分の処理が実行されます...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2")) {
                // プレイヤーが /basic2 を実行すると、この部分の処理が実行されます...
		if (!(sender instanceof Player)) {
			sender.sendMessage("このコマンドはゲーム内から実行してください。");
		} else {
			Player player = (Player) sender;
			// コマンドの実行処理...
		}
		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 (プラグインのメインクラス):

private static Plugin instance;

@Override
public void onEnable() {
	instance = this;
	// ...

	// plugin.yml に basic というコマンドを定義していないと、
        //実行した時にNullPointerExceptionが発生します。注意してください。
	getCommand("basic").setExecutor(new MyPluginCommandExecutor());

	// ...
}

public static Plugin getInstance() {
	return instance;
}

MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {
	private Plugin instance = MyPlugin.getInstance(); 
        // メインクラスの参照です。処理の中でメインクラスのメソッドを利用しない場合は、省略して構いません。

	@Override
	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
		// コマンドの実行内容...
	}
}

どのように、メインプラグインのインスタンスがMyPluginCommandExecutorのコンストラクタを実行するかに注目しましょう。

この節で紹介した方法により、メインのonCommand()メソッドが巨大で複雑になったとしても簡単に整理する事ができ、結果として、プラグインのメインクラスを複雑化させずに、処理分割する事ができます。

Note: プラグインが複数のコマンドを持つ場合、個々のコマンドに対応するCommandExecutorをコーディングする必要があります。
訳者補記: 積極的に、大きな処理は小さく分割していきましょう。そのための仕組みをBukkitが用意しています。という事を教示しています。

堅牢なonCommandの記述

onCommand()メソッドを記述する際、パラメタの利用に関して、
決め付けて掛かってはいけない事がありますので念頭において下さい。
これらに留意した処理を記述する事で、堅牢なonCommand()をコーディングする事ができます。

senderの内部データ型をチェックする

senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。
処理例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
    if ((sender instanceof Player)) {
        Player player = (Player) sender;
        // doSomething
    } else {
        sender.sendMessage(ChatColor.RED + "ゲーム内から実行してください!");
        return false;
    }
    return false;
}

コマンドのパラメタ長をチェックする

senderインスタンスは、妥当なパラメタを持っているとは限りません。パラメタ長をチェックして下さい。
処理例:

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if (args.length > 4) {
           sender.sendMessage(ChatColor.RED + "パラメタが多すぎます!");
           return false;
        } 
        if (args.length < 2) {
           sender.sendMessage(ChatColor.RED + "パラメタが足りません!");
           return false;
        }
}

プレイヤーがオンラインである事を確認する

特定のプレイヤーのPlayerインスタンスを利用したい場合、
必ずそのプレイヤーがオンラインである必要があります。
オンラインであるかどうかをチェックして下さい。
処理例:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
    Player other = getPlayer(args[0]);
    if (other == null) {
        sender.sendMessage(ChatColor.RED + args[0] + "さんはオフラインです!");
        return false;
    }
    return false;
}

private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的には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: false

doorman.*権限は、いくつかの子権限を含んでいます。 doorman.*権限がtrueである場合に、 その子権限はplugin.ymlに定義された値(trueかfalse)をデフォルト権限として機能します。 doorman.*権限がfalseである場合は、 その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。

独自の権限設定

独自に権限の仕組みを提供するプラグインを開発したい場合、 権限プラグインの開発方法を参考にして下さい。

スケジューリングタスクとバックグラウンドタスク

現在、Minecraftサーバはゲームロジックのほとんどがシングルスレッドで稼動しています。
このため、発生する個々の処理はごく小さいものにする必要があります。
プラグイン中に複雑なコードが存在して、それが適切に処理されないような場合は、
ゲームロジックに多大なラグや処理遅延を発生させる原因となります。

幸運なことに、Bukkitはプラグインに対してスケジューリングのためのコーディング方法を提供しています。
どこかのタイミングで一度だけRunnableタスクを実行したり、
小さなタスクに分けた定期的な繰り返しであったり、
新規に独立したスレッドを派生させたり・・・
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。

詳しくは、スケジューラのプログラミング中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。

ブロックの操作

ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、
まずはそのブロックを取得した上で、変更を加えることになります。
PlayerMoveイベントの処理中でこれを行う例を示します:

public void onPlayerMove(PlayerMoveEvent evt) {
    // プレイヤーの位置を取得します。
    Location loc = event.getPlayer().getLocation();
    // 位置のY座標を+5します。位置情報を変更しているだけで、実際にプレイヤーの位置が移動するわけではないことに注意してください。
    loc.setY(loc.getY() + 5);
    // 指定位置のブロックを取得します。
    Block b = loc.getBlock();
    // ブロックの種類に石(STONE)を設定します。
    b.setType(Material.STONE);
}

このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、
プレイヤーの5ブロック上方のブロックがStoneに変化する動作として見えるでしょう。
ブロック取得までの流れは・・・

  1. 取得したプレイヤーの位置から、ワールドを取得する
  2. 位置のY座標を+5する
  3. loc.getBlockAt(loc);で、指定位置に存在するブロックを得る

となります。

位置(loc)中のブロックインスタンス(b)に対して、
Materialを変えたり、ブロックデータ自体を変更する事もできます。 詳しくは、JavaDocを参照してください。

建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う一例を示します。
例えば、固体ブロックによる立方体を生成する処理は、
3階層にネストしたforループ処理によって記述できます。

public void generateCube(Location loc, int length){
        // 与えられたLocationから、立方体の端の座標を取得します。
        // getN()メソッドを使うと intへキャストする必要がありますが、
        // getBlockN()メソッドを使えばそのままintで座標を取得できます。
        int x1 = loc.getBlockX(); 
        int y1 = loc.getBlockY();
        int z1 = loc.getBlockZ();
     
        // 一辺の長さを足すことで、立方体の反対側の座標を計算します。
        int x2 = x1 + length;
        int y2 = y1 + length;
        int z2 = z1 + length;
     
        World world = loc.getWorld();
     
        // x座標方向のループ
        for (int xPoint = x1; xPoint <= x2; xPoint++) { 
            // y座標方向のループ
            for (int yPoint = y1; yPoint <= y2; yPoint++) {
                // z座標方向のループ
                for (int zPoint = z1; zPoint <= z2; zPoint++) {
                    // ループで処理する座標のブロックを取得します。
                    Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
                    // ダイアモンドブロックに設定します!
                    currentBlock.setType(Material.DIAMOND_BLOCK);
                }
            }
        }
    }

このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。
ただし、セットするブロックの種類はMaterial.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, inventorydiamondstack変数を用意して下さい。
inventoryはプレイヤーのインベントリで、diamondstackは(アイテムとしての)64個のダイヤモンドです。

次に、プレイヤーのインベントリがダイヤモンドを含んでいるかをチェックします。
プレイヤーがダイヤモンドをインベントリに所持している場合、
inventory.addItem(diamondstack)処理にて
同プレイヤーのインベントリに別のスタックを与え、黄金色のメッセージを送信します。
インベントリ操作は全然難しくありません。

ダイヤモンドのスタックを削除したい場合でも単純に、
inventory.addItem(diamondstack)
inventory.remove(diamondstack)に置き換えるだけです。
(メッセージにも少しイタズラしておきましょう)

アイテムの操作

アイテムはスタックという単位で操作します。
スタックデータに対する全ての操作は、ItemStackクラスの仕様を参照して下さい。

エンチャント

アイテムにエンチャントを付与するには、ItemStackクラスの addEnchantment(Enchantment enchant, int level) メソッドを使用します。

addEnchantment()メソッドでは、元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。
もし、通常ありえないエンチャントを設定したい場合は、addEnchantment()メソッドの代わりにaddUnsafeEnchantment()メソッドを使ってください。

// 木の棒のインスタンスを生成する
ItemStack myItem = new ItemStack(Material.STICK);

// 木の棒にFireAspectレベル100を付与する
//(ただしFireAspectレベル100は通常存在しないので、付与は成功しない) 
myItem.addEnchantment(Enchantment.FIRE_ASPECT, 100);

Metadata

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文のスクリプトを書く事にもなるはずです。

プラグインの配布

プラグインを実装し終わったら、Mavenを使ってビルドして、リリース用のJarファイルを作成してみましょう。

プロジェクトを右クリックして、実行>Maven install と選択してください。

Plugin deploy1.png

Eclipseのコンソールに、ビルド情報が流れます。
ビルドがうまくいけば、コンソールに「BUILD SUCCESS」と表示されてビルドが終了します。
もしビルドが失敗したなら、エラー情報を元に、エラーの解決を試みてください。

このチュートリアルで紹介したJDKが同梱のPleiadesを利用していない場合、MavenがJDKを見つけられずにエラーになっていることが多いです。
その場合は、Eclipseの設定を開いて、正しいJDKを選択しなおしてください。
Eclipseの設定は、「ウィンドウ」メニュー>設定 を選択し、開いたダイアログで、Java>インストール済みのJRE を選択します。
ここで、正しいバージョンのJDKが選択されていることを確認してください。


ビルドがうまくいった場合、プロジェクトのフォルダの中に target フォルダが作成されており、そのフォルダの中にビルドされたJarファイルがあります。

Plugin deploy2.png


プラグインのコードと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 = getPlayer(args[0]);
 
        // 対象プレイヤーが、オンラインかどうかを確認します。
        if (target == null) {
            sender.sendMessage(args[0] + " というプレイヤーが見つかりません!");
            return true;
        }
 
        // 対象プレイヤーを、1000tick(=50秒) の間、燃えるようにします。
        target.setFireTicks(1000);
        return true;
    }
    return false;
}

private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

プレイヤーを殺す

同じ要領で、プレイヤーを殺害するコマンドの例を紹介します。 onCommand()メソッドに記述します:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
        Player target = getPlayer(args[0]);
        // 対象プレイヤーがオンラインかどうかを確認します。
        if (target == null) {
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
            return true;
        }
        // 対象に1000ダメージを与える
        target.damage(1000);
    }
    return false;
}

private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

上記の拡張版として、プレイヤーを爆死させる処理を下記に示します:

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.damage(1000);

爆発を起こす

このコードは、TNTの爆発と同様の音とヴィジュアルを再現します。 これは、TNTの爆発効果は無効化しつつ、音とヴィジュアル効果を発生させる処理に転用できます。

@EventHandler
public void onExplosionPrime(ExplosionPrimeEvent event) {	
    Entity entity = event.getEntity();

    // このイベントは、点火されたTNTにより発生したのかどうかを確認します。
    // (つまり、TNTの爆発はこれで無効化されますが、クリーパーの爆発は無効化されません)
    if (entity instanceof TNTPrimed) {
        event.setCancelled(true); // イベントをキャンセルして、爆発を無かったことにする
        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 = getPlayer(args[0]);
        if (target == null) {
            sender.sendMessage("Player " + args[0] + " というプレイヤーは見つかりません!");
            return true;
        }
        // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。
        target.hidePlayer(s);
        return true;
    }
    return false;
}

private Player getPlayer(String name) {
    for ( Player player : Bukkit.getOnlinePlayers() ) {
        if ( player.getName().equals(name) ) {
            return player;
        }
    }
    return null;
}

クリックした場所に雷を落とす

下記のサンプルは、釣竿を持ってクリックしたときに、クリックした場所を取得して、その場所に雷を落とします。

@EventHandler
public void onPlayerInteractBlock(PlayerInteractEvent event) {

    Player player = event.getPlayer();

    if (player.getItemInHand().getType() == Material.FISHING_ROD) {
        // プレイヤーが見ている場所に雷をおとします。

        Block target = getTargetBlock(player);
        if (target != null) {
            target.getWorld().strikeLightning(target.getLocation());
        }
    }
}

private Block getTargetBlock(Player player) {
    
    // 視線上のブロックを100ブロック先まで取得
    BlockIterator it = new BlockIterator(player, 100);

    // 手前側から検証を行う。
    // Blockが取得できた時点でreturnして終了する。
    while ( it.hasNext() ) {

        Block block = it.next();

        if ( block.getType() != Material.AIR ) {
            // ブロックが見つかった
            return block;
        }
    }

    // 最後までブロックがみつからなかった
    return null;
}

リクエストに応じて書かれた記事

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が全快します。

プラグインのサンプル兼雛形

この内容について質問がある場合、遠慮なくAdamki11sBukkitDevのIRCチャンネル(当Wikiの原文を掲載しているサイトのIRCチャンネルです)で聞いてください。

Note: 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。その点を考慮して必ず原文にも目を通してから質問して下さい。