提供: Minecraft Modding Wiki
移動先: 案内検索
(パーミッション)
(<source lang="xml">...</source>の部分を<pre lang="xml">...</pre>に変更(xml内の<source>タグと競合していたため))
 
(11人の利用者による、間の144版が非表示)
1行目: 1行目:
 
本ページの内容は、[http://wiki.bukkit.org/ Bukkit Wiki]の[http://wiki.bukkit.org/Plugin_Tutorial Plugin Tutorial]を和訳した物となります。(一部は省略しています)<br />
 
本ページの内容は、[http://wiki.bukkit.org/ Bukkit Wiki]の[http://wiki.bukkit.org/Plugin_Tutorial Plugin Tutorial]を和訳した物となります。(一部は省略しています)<br />
 
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。<br />
 
最新ではない可能性があるため、より新しい情報を確認する場合は、本家を参照するようにして下さい。<br />
'''本項目は、和訳の最中です。最後まで読むことは出来ません。'''<br />
 
  
 
== 始めに ==
 
== 始めに ==
このチュートリアルは、[http://forums.bukkit.org/threads/plugin-development-a-huge-tutorial-status-under-development.15167/ こちらのスレッド]からの転載です。当ページの原著者は、'''Adamki11s'''氏ですが、多くの編集を受けて今に至ります。
+
:'''重要''': 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
 +
:'''重要''': 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
 +
:'''重要''': 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。当ページの内容を受けて原著者へ何らかのアクションを行う場合、その点を考慮して必ず原文にも目を通して下さい。
  
[http://forums.bukkit.org/members/adamki11s.42417/ Adamki11s Bukkit Profile Page]
+
このチュートリアルは、Bukkitプラグインを開発するための基本的なノウハウを網羅しております。
  
このチュートリアルを読み終えた後は、'''Adamki11s''''氏の"Extras" からより深い情報を得る事が出来ます。[http://forums.bukkit.org/threads/dev-extras-v1-1-additional-useful-and-advanced-methods-for-plugin-developers-953.26207/ Extras library forums thread.]
+
Javaを習得する、IDEで開発環境を構築する、などの非常に基本的な部分から説明が始まっていますが、既にご理解頂いている場合は読み飛ばしていただいて構いません。
 
 
 
 
:'''Note''': 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
 
:'''Note''': 当ページの内容は、[[Bukkit用MODの作成方法]]の内容とは異なるアプローチによるBukkitプラグイン開発手法の解説です。
 
:'''Note''': 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。
 
  
 
== Javaの習得 ==
 
== Javaの習得 ==
20行目: 16行目:
 
このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。<br/>
 
このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。<br/>
 
Javaの基礎知識が少ない方は、下記の情報に触れてください。
 
Javaの基礎知識が少ない方は、下記の情報に触れてください。
 
=== Javaコーディング方法 ===
 
  
 
*[http://docs.oracle.com/cd/E26537_01/tutorial/index.html Oracleの記事] (現在のJavaの公式サイト)  
 
*[http://docs.oracle.com/cd/E26537_01/tutorial/index.html Oracleの記事] (現在のJavaの公式サイト)  
38行目: 32行目:
  
 
あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。<br/>
 
あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。<br/>
Eclipseのお勧めのバージョンの配布元は、[http://mergedoc.sourceforge.jp/pleiades_distros3.7.html 日本語 Eclipse / Pleiades All in One 日本語ディストリビューション]です。<br/>
+
Eclipseのお勧めのバージョンの配布元は、[http://mergedoc.sourceforge.jp/ 日本語 Eclipse / Pleiades All in One 日本語ディストリビューション]です。<br/>
 
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。
 
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。
  
どのIDEも気に入らなければ、BlueJも利用できます。<br/>
+
Pleiadesを利用する場合、「開発対象用 JDK」と、ビルドツールMavenが実行できるプラグイン「m2e」が同梱されているものを選択してください。<br/>
プラグイン開発が初めてであるなら、とにかく一度Eclipseを使ってみて、<br/>
+
(Eclipseを既に利用している場合でも、m2eを後から追加インストールすることは可能です。)
良ければNetBeansを試してみると良いでしょう。
 
  
[http://www.okapiproject.com/java/java_tools/eclipse/index.html 優良なEclipseの解説]。お勧めのものより古いバージョンの解説ですが、ほぼ同様です。
+
spigotなどのBukkitサーバーの実行環境を利用する場合、BuildTools.jar を利用してサーバー実行環境をビルドする必要がありますが、Mavenが利用できる場合は、Mavenが開発用のライブラリを自動でダウンロードするため、BuildTools.jar を使わなくても開発環境を構築することが可能です。<br/>
 +
ただし、当然ですが、デバッグ実行するための実行環境は必要になりますから、BuildTools.jar を利用して実行環境も準備しておいてください。このチュートリアルでは、BuildTools.jar を利用した実行環境の構築は割愛いたします。
 +
<!--
 +
もし、BuildTools.jar を利用した実行環境の構築について、きれいにまとめられている資料があれば、ここにリンクを置いてください。
 +
-->
  
 
== Plugin用プロジェクトを始めるために ==
 
== Plugin用プロジェクトを始めるために ==
  
 
=== プロジェクトの作成 ===
 
=== プロジェクトの作成 ===
始める前にEclipseのワークスペースとファイルの設定を行う必要があります。<br />
+
始めるために、新しいワークスペースを作成する必要があります。<br />
Eclipseを起動し、ファイル>新規>Java プロジェクトと選択して下さい。<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 プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?
 +
 
 +
「ビルドプランを計算できませんでした。」と出た場合、作成したパッケージを右クリックし「実行」から「Maven install」をクリックすることにより、
 +
Mavenのインストールが始まりエラーが出なくなります。
 +
 
 +
=== Bukkit API の参照設定 ===
 +
 
 +
次に、左側に作成された TutorialPlugin プロジェクトの中にある、「pom.xml」というファイルをダブルクリックしてください。そして、画面右下のあたりにある「pom.xml」というタブをクリックしてください。
 +
 
 +
[[Image:create_project4.png]]
 +
 
 +
まず、Java開発環境(JDK)の参照設定をします。<br/>
 +
一番最後の行に '''</project>''' というタグがありますが、その1行上に、次の内容を挿入してください。<br/>
 +
(なお、これは Java 7 を参照してビルドするための設定です。もし Java 6 を参照してビルドしたい場合は、sourceタグとtargetタグに書かれている 1.7 のところを 1.6 に変更してください。また、Java 8を参照したい場合は、1.7を1.8に変更してください。)
 +
 
 +
<pre lang="xml">
 +
  <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>
 +
</pre>
 +
 
 +
次に、Bukkit APIを実装しているSpigotのリポジトリがどこにあるかURLで示します。<br/>
 +
下記の内容を追記してください。
 +
 
 +
<pre lang="xml">
 +
  <repositories>
 +
    <repository>
 +
      <id>spigot-repo</id>
 +
      <url>https://hub.spigotmc.org/nexus/content/groups/public</url>
 +
    </repository>
 +
  </repositories>
 +
</pre>
 +
 
 +
最後に、Bukkit API の参照設定を追加します。<br/>
 +
下記の内容を追記してください。
 +
 
 +
<pre lang="xml">
 +
  <dependencies>
 +
    <dependency>
 +
      <groupId>org.bukkit</groupId>
 +
      <artifactId>bukkit</artifactId>
 +
      <version>1.10.2-R0.1-SNAPSHOT</version>
 +
    </dependency>
 +
  </dependencies>
 +
</pre>
 +
 
 +
この設定では、Bukkit API 1.10.2-R0.1-SNAPSHOT が参照されます。<br/>
 +
別のバージョンを参照したい場合は、versionタグの中を変更してください。<br/>
 +
設定が可能なバージョン番号の一覧は、[https://hub.spigotmc.org/nexus/content/groups/public/org/bukkit/bukkit/ こちら] を参照してください。
 +
 
 +
ここまで編集をすると、pom.xmlは次のようになっているはずです。確認してみてください。
 +
 
 +
<pre lang="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>spigot-repo</id>
 +
      <url>https://hub.spigotmc.org/nexus/content/groups/public</url>
 +
    </repository>
 +
  </repositories>
 +
  <dependencies>
 +
    <dependency>
 +
      <groupId>org.bukkit</groupId>
 +
      <artifactId>bukkit</artifactId>
 +
      <version>1.10.2-R0.1-SNAPSHOT</version>
 +
    </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」と入力して該当クラスを検索して選択しても構いません。)
 +
 
 +
完了 を押して、新規クラスが作成されたことを確認して下さい。ソースコードは次のようになっているはずです。
 +
 
 +
<pre lang="java">
 +
package my.test.plugin.tutorialplugin;
  
[[Image:NewJavaProject.png]]
+
import org.bukkit.plugin.java.JavaPlugin;
  
プロジェクト名を入力し、新規プロジェクトウィザード画面の指示に従って、プロジェクトを作成してください。
+
public class TutorialPlugin extends JavaPlugin {
  
=== Bukkit APIの参照 ===
+
}
開発を始める前にbukkit APIライブラリをプロジェクトに追加する必要があります。
+
</pre>
使用したい他のAPIも同じように追加することが可能です。
 
  
最新のBukkit APIはここからダウンロードが可能です。 {{BDownload}}
+
{{warning}} メインクラスは、コンストラクタを実行したり、新しいインスタンスを作成したりしないでください。
  
画面の左手にあるパッケージエクスプローラの(先ほど名前を付けた)プロジェクトを右クリックし、プロパティーを選択します。<br />
+
=== plugin.ymlの作成 ===
開いた画面のJavaビルド・パスを選択し、ライブラリータブの中から、外部 Jar 追加ボタンを押して、ダウンロードしたBukkit APIを指定します。<br />
 
  
[[Image:BuildPathPic.png|800px]]
+
メインクラスが作成できたら、次は plugin.yml ファイルを作成します。<br/>
 +
plugin.yml ファイルは、プラグインとしてBukkitに読み込みされるときに、プラグインの設定を記述しておくファイルです。<br/>
 +
必須のファイルですから、必ず作成してください。
  
=== BukkitのJavadocの利用方法 ===
+
プロジェクトの ''src/main/resources'' を右クリックして、新規>ファイル を選択してください。
  
Eclipseを用いたJavaプログラミングの経験がある方なら、<br/>
+
[[Image:Create_pluginyml1.png]]
Eclipseのエディタ上でクラスやメソッドにマウスカーソルを重ねた時に、<br/>
 
クラスやメソッドに関するドキュメントがポップアップで表示される事をご存知でしょう。<br/>
 
これは、[http://download.oracle.com/javase/6/docs/api/ Oracleのウェブサイト]から得る事ができるJavadocの内容が表示されています。<br/>
 
  
Bukkitもまた、BukkitのAPIが提供するクラスやメソッドの各々のコードに[http://jd.bukkit.org/apidocs/ 同種のドキュメント]を含んでおり、ポップアップで表示させる事ができます。<br/>
+
開いたダイアログの ファイル名 の欄で、「plugin.yml」と入力し、完了を押してください。
Eclipse上で、Bukkitのクラスやメソッドにマウスを重ねたタイミングでポップアップを表示できるようにするためには、下記の手順を行います。
 
  
# プロジェクトエクスプローラ上で、Bukkitのjarファイルを右クリックして、メニューを開く。
+
[[Image:Create_pluginyml2.png]]
# "プロパティ"を選択し、表示されるポップアップ左側の"Javadoc ロケーション"項目を選択する。
 
# "Javadoc URL"の下部にあるテキストボックスに、"http://jd.bukkit.org/apidocs/" (ダブルクォートは除く)を貼り付ける。
 
# "検証"ボタンを押下し、URLがJavadocとして正しく識別される事をチェックしてから、OKボタンを押す。
 
  
:次ような画面になります:<br/>
+
''src/main/resources'' のところに、''plugin.yml'' が作成されたことを確認してください。<br/>
:[[Image:Bukkitjavadocs.png]]
+
作成された ''plugin.yml'' を、画面の右側へドラッグアンドドロップしてください。<br/>
 +
画面右側で ''plugin.yml'' の編集画面が開きます。
  
=== Plugin開発の開始 ===
+
''plugin.yml'' に、下記の3行を書いてください。
  
[[Image:MakePackage.png]]
+
<syntaxhighlight lang="yaml">
 +
name: (あなたのプラグイン名)
 +
main: (作成したパッケージ名).(作成したメインクラス)
 +
version: (あなたのプラグインのバージョン)
 +
</syntaxhighlight>
  
== onEnable() and onDisable() ==
+
このチュートリアルでは、次のように作成します。
このファンクションは、プラグインが有効/無効になったときに呼ばれます。<br />
+
 
 +
<syntaxhighlight lang="yml">
 +
name: TutorialPlugin
 +
main: my.test.plugin.tutorialplugin.TutorialPlugin
 +
version: 0.0.1
 +
</syntaxhighlight>
 +
 
 +
{{note}} メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。
 +
{{note}} コロン(:)のあとにワンスペース( )あるのに注意してください。
 +
 
 +
 
 +
plugin.yml の詳細な内容一覧は、[[plugin.ymlの設定一覧]] をご参照ください。
 +
 
 +
== onEnable()メソッドとonDisable()メソッド ==
 +
このメソッドは、プラグインが有効/無効になったときに、Bukkitから呼び出しされます。<br />
 
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。<br />
 
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。<br />
onEnable()は、プラグインがBukkitから読み込まれるときに最初に呼ばれ、プラグインを実行するために必須です。<br />
+
onEnable()は、Bukkitが起動するときに、プラグインが有効化されたときに呼び出されます。onDisable()は、Bukkitが停止するときに、プラグインが無効化されたときに呼び出されます。<br />
  
 
=== onEnable()とonDisable()の基本 ===
 
=== onEnable()とonDisable()の基本 ===
 
前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。<br />
 
前のセクションで作成したメインクラスに、onEnable()とonDisable()のメソッドを作成します。<br />
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
public void onEnable(){
+
package my.test.plugin.tutorialplugin;
 +
 +
import org.bukkit.plugin.java.JavaPlugin;
 
   
 
   
}
+
public class TutorialPlugin extends JavaPlugin {
 
   
 
   
public void onDisable(){  
+
    @Override
 +
    public void onEnable() {
 +
        // TODO ここに、プラグインが有効化された時の処理を実装してください。
 +
    }
 
   
 
   
 +
    @Override
 +
    public void onDisable() {
 +
        // TODO ここに、プラグインが無効化された時の処理を実装してください。
 +
    }
 
}
 
}
 
</source></blockquote>
 
</source></blockquote>
  
  
現時点では、このメソッドは何も行いません。また、エラーが発生する事にも気付くでしょう。このエラーは、メインクラスが、プラグインの機能を継承(extends)する必要がある事を示しています。当クラスの定義文を、下記のように変更しましょう。
+
現時点では、このメソッドは何も行いません。
  
これを・・・
+
=== Loggerを利用したメッセージ出力 ===
<blockquote><source lang="java">Class <classname> {}</source></blockquote>
 
  
このように変更する。
+
始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。<br/>
<blockquote><source lang="java">Class <classname> extends JavaPlugin {}</source></blockquote>
+
ログを出力するには、''getLogger()''メソッドを実行してLoggerを取得し、そのinfoメソッドを呼び出します。
  
すると、前述の追加コードが赤の下線でハイライト表示され、何かが間違っていることを通知してくるはずです。このハイライト部にマウスを重ねると、下記のようなポップアップが表示されるので、「'JavaPlugin'をインポートします(org.bukkit.plugin.java)」を選択します。
+
<blockquote><source lang="java">
 
+
getLogger().info("onEnableメソッドが呼び出されたよ!!");
<blockquote>[[ファイル:to_need_import_package.png]]</blockquote>
+
</source></blockquote>
  
もしくは、
+
onDisable()メソッドについても同等の記述を行います。<br/>
<blockquote><source lang="java">import org.bukkit.plugin.java.JavaPlugin;</source></blockquote>
+
メインクラスに、次のように実装してみてください。
をコード上部の定義部に記述する事でも同様の事が行えます。
 
  
=== Loggerを利用したメッセージ出力 ===
 
 
始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。この処理として、ログ出力機能(Logger)をメインクラスに定義して初期化します。
 
<blockquote><source lang="java">Logger log = this.getLogger();</source></blockquote>
 
その後、onEnable()メソッドに、プラグインが動作している事を通知するメッセージを出力する処理を記述します。
 
<blockquote><source lang="java">log.info("Your plugin has been enabled.");</source></blockquote>
 
 
onDisable()メソッドについても同等の記述を行います。ここまでのコーディングによって、メインクラスは下記の様な内容になっているはずです。
 
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
package me.<yourname>.<pluginname>;
+
package my.test.plugin.tutorialplugin;
 
   
 
   
import java.util.logging.Logger;
 
 
import org.bukkit.plugin.java.JavaPlugin;
 
import org.bukkit.plugin.java.JavaPlugin;
 
   
 
   
public class <classname> extends JavaPlugin {
+
public class TutorialPlugin extends JavaPlugin {
 
   
 
   
Logger log;
+
    @Override
 +
    public void onEnable() {
 +
        getLogger().info("onEnableメソッドが呼び出されたよ!!");
 +
    }
 
   
 
   
public void onEnable(){
+
    @Override
log = this.getLogger();
+
    public void onDisable() {
log.info("Your plugin has been enabled!");
+
        getLogger().info("onDisableメソッドが呼び出されたよ!!");
}
+
    }
 
public void onDisable(){
 
log.info("Your plugin has been disabled.");
 
}
 
 
}
 
}
 
</source></blockquote>
 
</source></blockquote>
 +
 +
ここまで作ったところで、[[#プラグインの配布|プラグインのJarファイルの発行]] を行ってプラグインを作成し、実際にCraftBukkitのpluginsフォルダへ導入して、コンソールに設定したログメッセージが表示されることを確認してみてください。
 +
 +
=== Reloadを制御する ===
 +
 +
サーバーが終了処理をするときや、開始処理をするときに限らず、
 +
サーバーが /reload コマンドにより、プラグインがdisableされenableされる動作について理解しておくことも重要です。<br/>
 +
サーバーが開始したときについてだけ初期化処理を考慮することは危険です。<br/>
 +
なぜなら、プレイヤーは既にオンライン状態で接続していますし、ワールドデータやチャンクは既にロードされていますし、他にもいろいろ想定していない違いがあります。
 +
 +
* /reload が実行されると、プラグインとしては、onDisable() が実行されて、onEnable() が実行されます。
 +
* staticで保持していないデータは、全て失われます。
 +
* 上記に書いてあるように、プレイヤーやワールドやチャンクは既にロード済みの状態です。
  
 
== イベントとリスナ ==
 
== イベントとリスナ ==
161行目: 365行目:
 
さて、どのようにイベントを登録しいつ発生するかについて理解しました。
 
さて、どのようにイベントを登録しいつ発生するかについて理解しました。
 
しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。
 
しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。
このメソッドは、プレイヤーが文字"/"を入力する度に実行されます。
+
このメソッドは、plugin.ymlに設定したプラグインのコマンドが実行されたときに呼び出されます。
具体的には、"/do something"とコマンドを実行した場合にonCommand()が呼び出されます。
 
 
今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。
 
今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。
  
170行目: 373行目:
 
具体的には、"give"コマンドは既にいくつかのプラグインで利用されています。
 
具体的には、"give"コマンドは既にいくつかのプラグインで利用されています。
 
もし独自に"give"コマンドを実装した場合は、
 
もし独自に"give"コマンドを実装した場合は、
"give"コマンドを実装している他のプラグインとの互換性は無くなります。
+
"give"コマンドを実装している他のプラグインとの互換性は無くなります。<ref group="注">実際には、相互のコマンドを呼び分けるために特定のプレフィクスがコマンドの前に付与されます。しかしそれでも、単にgiveコマンドを呼んだだけではどのコマンドが呼ばれるかわからないことには変わりありません。</ref>
  
onCommand()は、常にboolean型の値としてtrue,falseのどちらかを戻り値として返さねばなりません。
+
onCommand()は、常にboolean型の値<ref group="注">trueかfalseのどちらか</ref>を戻り値として返さねばなりません。
 
trueを返した場合は、情報表示のためのイベントは発生しません。
 
trueを返した場合は、情報表示のためのイベントは発生しません。
 
falseを返した場合は、プラグインファイルを"usage: property"に戻し、コマンドを実行したプレイヤーに、コマンドの利用方法を通知するメッセージを表示します。
 
falseを返した場合は、プラグインファイルを"usage: property"に戻し、コマンドを実行したプレイヤーに、コマンドの利用方法を通知するメッセージを表示します。
181行目: 384行目:
 
* Command cmd - 実行されたコマンドの内容
 
* Command cmd - 実行されたコマンドの内容
 
* String commandLabel - 利用されたコマンドエイリアス
 
* String commandLabel - 利用されたコマンドエイリアス
* String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)
+
* String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)<ref group="注">半角空白 (U+0020)で分割されて、配列に格納される</ref>
  
 
=== コマンドの設定 ===
 
=== コマンドの設定 ===
210行目: 413行目:
 
       description: This is a demo command.
 
       description: This is a demo command.
 
       usage: /<command> [player]
 
       usage: /<command> [player]
       permission: <plugin name>.basic
+
       permission: tutorialplugin.basic
 
       permission-message: You don't have <permission>
 
       permission-message: You don't have <permission>
 
</source>
 
</source>
* basic - コマンド名
+
* basic - コマンド名。
* description - コマンドの説明文
+
* description - コマンドの説明文。
* usage - onCommand()がfalseを返した際に、コマンド実行ユーザに向けて表示されるメッセージの内容。コマンドの使い方を明解に書いてください。
+
* usage - コマンドの使い方。onCommand() でfalseを返したときに、コマンド実行ユーザに向けて表示されるメッセージの内容。あなたの作ろうとしているコマンドの使い方を、わかりやすく説明してください。
* permission - 当コマンドの動作に必要なプラグインの設定を、コマンド実行ユーザに知らせるメッセージ。(主に、コマンド実行に必要なpermissionを書きます)
+
* permission - コマンドの実行権限。このコマンドの実行に必要なパーミッションノードを設定します。あなたのプラグイン名と、コマンド名を、ピリオド(.)でつなげたパーミッションノードを設定することを推奨します(例:myplugin.test)。
* permission-message - コマンド実行権限を持たないユーザがコマンドを実行した場合に、その旨を同ユーザに知らせるメッセージ。
+
* permission-message - 上で設定したコマンド実行権限を持たないユーザがコマンドを実行した場合に、実行権限が無いことを同ユーザに知らせるメッセージ。
 
</blockquote>
 
</blockquote>
  
:'''Note''': ymlファイルには、1個タブを2個のスペースで記述する必要があります。タブ文字は構文エラーとなるため利用できません。
+
なお、usage の欄では <command>、permission-message の欄では <permission> というキーワードをそのまま指定できます。それぞれ、設定したコマンド名と、設定したパーミッションノードに置き換えられます。<br>
 +
詳しい書き方は、[[plugin.ymlの設定一覧#コマンドのオプション設定|plugin.ymlの書き方の、コマンドのオプション設定の節]] を参照してください。
 +
 
 +
:'''Note''': ymlファイルには、インデントに2個以上の半角スペースを記述する必要があります。タブ文字は構文エラーとなるため利用できません。
  
 
=== コンソールコマンドとプレイヤーコマンド ===
 
=== コンソールコマンドとプレイヤーコマンド ===
234行目: 440行目:
 
記述例:  
 
記述例:  
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
 +
@Override
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
Player player = null;
+
if (cmd.getName().equalsIgnoreCase("basic")) {  
if (sender instanceof Player) {
+
// プレイヤーが /basic を実行すると、この部分の処理が実行されます...
player = (Player) sender;
 
}
 
 
 
if (cmd.getName().equalsIgnoreCase("basic")){ // If the player typed /basic then do the following...
 
// do something...
 
 
return true;
 
return true;
 
} else if (cmd.getName().equalsIgnoreCase("basic2")) {
 
} else if (cmd.getName().equalsIgnoreCase("basic2")) {
if (player == null) {
+
                // プレイヤーが /basic2 を実行すると、この部分の処理が実行されます...
sender.sendMessage("this command can only be run by a player");
+
if (!(sender instanceof Player)) {
 +
sender.sendMessage("このコマンドはゲーム内から実行してください。");
 
} else {
 
} else {
// do something else...
+
Player player = (Player) sender;
 +
// コマンドの実行処理...
 
}
 
}
 
return true;
 
return true;
253行目: 457行目:
 
return false;
 
return false;
 
}
 
}
</source> </blockquote>  
+
</source> </blockquote>
  
 
この例では、'''basic''' コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、'''basic2''' コマンドは、ログインプレイヤーしか実行できません。
 
この例では、'''basic''' コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、'''basic2''' コマンドは、ログインプレイヤーしか実行できません。
274行目: 478行目:
  
 
:'''Note''': <code>'''"basic"'''</code>は実行されたコマンドであり、<code>'''myExecutor'''</code>はMyPluginCommandExecutorインスタンスです。
 
:'''Note''': <code>'''"basic"'''</code>は実行されたコマンドであり、<code>'''myExecutor'''</code>はMyPluginCommandExecutorインスタンスです。
 
 
とっても良い具体例:
 
とっても良い具体例:
<blockquote><source lang="java">MyPlugin.java (the main plugin class):
 
  
private MyPluginCommandExecutor myExecutor;
+
MyPlugin.java (プラグインのメインクラス):
 +
<blockquote><source lang="java">
 
@Override
 
@Override
 
public void onEnable() {
 
public void onEnable() {
// ....
+
// plugin.yml に basic というコマンドを定義していないと、
+
        //実行した時にNullPointerExceptionが発生します。注意してください。
myExecutor = new MyPluginCommandExecutor(this);
+
getCommand("basic").setExecutor(new MyPluginCommandExecutor(this));
getCommand("basic").setExecutor(myExecutor);
+
}
+
</source></blockquote>
// ...
 
}</source></blockquote>
 
  
 +
MyPluginCommandExecutor.java:
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
MyPluginCommandExecutor.java:
+
public class MyPluginCommandExecutor implements CommandExecutor {
 +
 
 +
        // メインクラスの参照です。処理の中でメインクラスのメソッドを利用しない場合は、省略して構いません。
 +
private final MyPlugin instance;
  
public class MyPluginCommandExecutor implements CommandExecutor {
 
 
private MyPlugin plugin;
 
 
 
public MyPluginCommandExecutor(MyPlugin plugin) {
 
public MyPluginCommandExecutor(MyPlugin plugin) {
 
this.plugin = plugin;
 
this.plugin = plugin;
 
}
 
}
+
 
 
@Override
 
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
// ... implementation exactly as before ...
+
// コマンドの実行内容...
 
}
 
}
 
}
 
}
322行目: 523行目:
 
senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。<br/>
 
senderの内部データがPlayer型であるとは限らない事を念頭において、チェックを行って下さい。<br/>
 
処理例:
 
処理例:
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
+
<blockquote><source lang="java">
if ((sender instanceof Player)) {
+
@Override
          // doSomething
+
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
        } else {
+
    if (sender instanceof Player) {
          sender.sendMessage(ChatColor.RED + "You must be a player!");
 
          return false;
 
        }
 
 
         Player player = (Player) sender;
 
         Player player = (Player) sender;
 +
        // ここに、処理を実装する。
 +
        return true;
 +
    } else {
 +
        sender.sendMessage(ChatColor.RED + "ゲーム内から実行してください!");
 
         return false;
 
         return false;
}</source> </blockquote>
+
    }
 +
    return false;
 +
}
 +
</source> </blockquote>
 +
 
 +
このように、'''<code>if (sender instanceof Player)</code>''' で必ずPlayerであることをチェックしないと、プレイヤーでないコマンド実行者(例えばコンソール)がコマンドを実行したときに、'''<code>Player player = (Player) sender</code>''' のところで処理が失敗してしまいます。
  
 
=== コマンドのパラメタ長をチェックする ===
 
=== コマンドのパラメタ長をチェックする ===
338行目: 545行目:
 
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
 
if (args.length > 4) {
 
if (args.length > 4) {
           sender.sendMessage(ChatColor.RED + "Too many arguments!");
+
           sender.sendMessage(ChatColor.RED + "パラメタが多すぎます!");
 
           return false;
 
           return false;
 
         }  
 
         }  
 
         if (args.length < 2) {
 
         if (args.length < 2) {
           sender.sendMessage(ChatColor.RED + "Not enough arguments!");
+
           sender.sendMessage(ChatColor.RED + "パラメタが足りません!");
 
           return false;
 
           return false;
 
         }
 
         }
348行目: 555行目:
  
 
=== プレイヤーがオンラインである事を確認する ===
 
=== プレイヤーがオンラインである事を確認する ===
特定のプレイヤーのPlayerインスタンスを利用したい場合、<br/>
+
特定のプレイヤーのPlayerインスタンスを利用したい場合、必ずそのプレイヤーがオンラインである必要があります。<br/>
必ずそのプレイヤーがオンラインである必要があります。<br/>
 
 
オンラインであるかどうかをチェックして下さい。<br/>
 
オンラインであるかどうかをチェックして下さい。<br/>
 +
Bukkit.getPlayer(プレイヤー名) または、Bukkit.getPlayerExact(プレイヤー名) で、オンラインのプレイヤーを取得できます。<br/>
 +
(Bukkit.getPlayer はプレイヤー名と前方一致で、Bukkit.getPlayerExact は完全一致で、プレイヤーを取得します。)<br/>
 +
もし、指定したプレイヤーがオフラインだった場合は、nullが返されます。<br/>
 
処理例:
 
処理例:
<blockquote><source lang="java">public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
+
 
Player other = (Bukkit.getServer().getPlayer(args[0]));
+
<blockquote><source lang="java">
         if (other == null) {
+
@Override
          sender.sendMessage(ChatColor.RED + args[0] + " is not online!");
+
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
          return false;
+
    if (args.length < 1) {
        }
+
         sender.sendMessage("コマンドの後にプレイヤー名を指定してください!");
 
         return false;
 
         return false;
}</source> </blockquote>  
+
    }
 +
    Player target = Bukkit.getPlayerExact(args[0]);
 +
    if ( target == null ) {
 +
        sender.sendMessage(ChatColor.RED + args[0] + "さんはオフラインです!");
 +
        return true;
 +
    } else {
 +
        sender.sendMessage(ChatColor.AQUA + args[0] + "さんはオンラインです!");
 +
        return true;
 +
    }
 +
}
 +
</source> </blockquote>  
  
 
オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的には<code>'''OfflinePlayer'''</code>クラスを利用して行う事ができます。
 
オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的には<code>'''OfflinePlayer'''</code>クラスを利用して行う事ができます。
366行目: 585行目:
 
:: ''[[新しいConfigurationの使い方]]を参照して下さい ''
 
:: ''[[新しいConfigurationの使い方]]を参照して下さい ''
  
== パーミッション ==
+
== 権限 ==
Bukkitのパーミッション(権限)APIの利用は、簡単ではありません。
+
:'''Note''': この節は、日本語読者向けに独自の内容を記述しています。
  
プレイヤーが特定の権限を持っているかどうかを調べる処理は次のようになります:
+
Bukkitにおける権限の利用は、とっても簡単です!
<blockquote><source lang="java">if(player.hasPermission("some.pointless.permission")) {
 
  //Do something
 
}else{
 
  //Do something else
 
}</source></blockquote>
 
  
権限がセットされているか、いない(Javaの'''null'''と同等)かを調べる処理は次のようになります:
+
権限がセットされているか、いない(Javaの'''null'''と同等)かを調べるには以下のメソッドを利用します:
 
<blockquote><source lang="java">boolean isPermissionSet(String name)</source></blockquote>  
 
<blockquote><source lang="java">boolean isPermissionSet(String name)</source></blockquote>  
  
なぜグループの概念が存在しないかと思った方もいるでしょうが、
+
また、権限を持っているかどうかを調べるには以下のメソッドを利用します:
そもそも、権限にはグルーピングの概念は不要なのです。<br/>
+
<blockquote><source lang="java">boolean hasPermission(String name)</source></blockquote>  
  
元々、グループの主な用途はチャットメッセージをフォーマッティングする事にありました。<br/>
+
これを利用してプレイヤーが特定の権限を持っているかどうかを調べる処理は次のようになります:
これは、権限の機能を利用してもっと簡単に行えます。<br/>
+
<blockquote><source lang="java">if(player.hasPermission("some.pointless.permission")) {
例えば、チャットプラグインの設定において、権限とプレフィクスの関連を定義するはずです。<br/>
+
  player.sendMessage("あなたは権限を持っています。");
具体的には、権限'''"someChat.prefix.admin"'''をプレフィクス'''[Admin]'''に対応させるような定義で、<br/>
+
}else{
これによってプレイヤーがチャットで発言する度に、プレイヤー名の先頭に'''[Admin]'''が付くようになります。
+
  player.sendMessage("あなたは権限を持っていません。");
 
+
}</source></blockquote>
他にも、グループに所属する複数のユーザに、<br/>
 
メッセージを送信するような機能を実現するために利用する事が考えられます。<br/>
 
この例を、権限を利用した処理で記述すると以下のようになります:
 
<blockquote><source lang="java">for(Player player: getServer().getOnlinePlayers()) {
 
  
    if(player.hasPermission("send.recieve.message")) {
+
2メソッドの違いは、権限がセットされていなくてもデフォルトで与えられていれば権限を持っているという扱いになるかどうかです。<br>
        player.sendMessage("You were sent a message");
+
hasPermissionでは デフォルトで与えられていればtrueが返ります。
    }
 
  
}</source> </blockquote>  
+
PermissionsExなどで 権限はグループとして扱われることが多いですが、Bukkitには権限をグループとして扱う機能はありません。<br/>
 +
グループとして扱う場合は、別途 上記のようなプラグインを使うか、自分で作る必要があります。
  
さて、依然として<br/>
+
=== 権限の設定 ===
「グループを利用せずに、複数のプレイヤーに権限をセットする良い方法は何なのか?」が疑問かと思いますが・・・<br/>
 
BukkitのAPI自体は、グループの概念を提供していません。<br/>
 
グループの概念を利用するためには、permissionsBukkitのような<br/>
 
グループ権限の機能を提供するプラグインを利用する事になります。<br/>
 
とどのつまり、'''このAPIはインタフェース(Interface)であり、実装(Implementation)ではない'''のです。
 
  
=== Configuring your permissions  ===
+
デフォルトで権限を付与させたり、OPにのみデフォルトで付与させたい場合は、<br>
 +
''plugin.yml''を使うと簡単にできます。
  
If you want more control over your permissions, for example default values or children then you should consider adding them to your ''plugin.yml''. This is completely optional, however it is advised. Below is an example permissions config that would be appended to the end of your existing ''plugin.yml'':
+
下記は、''plugin.yml''の最後に追加する形で設定する権限です:
<blockquote><code><source lang="yaml">permissions:
+
<blockquote>
 +
<syntaxhighlight lang="yaml">
 +
permissions:
 
     doorman.*:
 
     doorman.*:
 
         description: Gives access to all doorman commands
 
         description: Gives access to all doorman commands
427行目: 635行目:
 
         default: true
 
         default: true
 
     doorman.denied:
 
     doorman.denied:
         description: Prevents this user from entering the door</source> </code> </blockquote>  
+
         description: Prevents this user from entering the door
Firstly, each permission your plugin uses is defined as a child node of the ''permissions'' node. Each permission can then optionally have a description, a default value, and children.
+
</syntaxhighlight>
 +
</blockquote>
 +
 
  
==== Defaults  ====
+
まず、プラグインが利用する各権限を、''permissions''ノードの子ノードとして定義します。<br/>
 +
それぞれの権限はオプションとして、概要(description)、デフォルト値、子ノードを持ちます。
  
By default when a permission isn't defined for a player&nbsp;''hasPermission''&nbsp;will return false. Inside your plugin.yml you can change this by setting the default node to be one of four values:
+
==== デフォルト権限 ====
  
*'''true''' - The permission will be true by default.
+
プレイヤーが権限を持たない場合、&nbsp;''hasPermission''&nbsp;はデフォルトではfalseを返します。<br/>
*'''false''' - The permission will by false by default.
+
''plugin.yml''中のデフォルトのノードに対して、下記の4つのどれか1つを設定する事で、<br/>
*'''op''' - If the player is an op then this will be true.
+
この挙動を変更する事ができます:
*'''not op''' - If the player is not an op then this will be true.
+
*'''true''' - デフォルトで、権限を持つ(=hasPermissionがtrueを返す)。
 +
*'''false''' - デフォルトで、権限を持たない(=hasPermissionがfalseを返す)。
 +
*'''op''' - プレイヤーがOPであれば、権限を持つ(=hasPermissionがtrueを返す)。
 +
*'''not op''' - プレイヤーがOPでなければ、権限を持つ(=hasPermissionがtrueを返す)。
  
==== Children  ====
+
==== 子権限 ====
  
Before now you will probably be used to the * permission to automatically assign all sub permissions. This has changed with the bukkit API and you can now define the child permissions. This allows for a lot more flexibility. Below is an example of how you do this:
+
権限を設定するときに''* 権限''を利用すると、その権限の子権限すべてを操作することができます。<br/>
<blockquote><source lang="yaml">permissions:
+
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、<br/>
 +
高い柔軟性を提供しています。<br/>
 +
下記はその実装例です:
 +
<blockquote>
 +
<syntaxhighlight lang="yaml">
 +
permissions:
 
     doorman.*:
 
     doorman.*:
 
         description: Gives access to all doorman commands
 
         description: Gives access to all doorman commands
449行目: 668行目:
 
             doorman.ban: true
 
             doorman.ban: true
 
             doorman.knock: true
 
             doorman.knock: true
             doorman.denied: false</source> </blockquote>  
+
             doorman.denied: false</source>
Here the ''doorman.*'' permission has several child permissions assigned to it. The way child permissions work is when ''doorman.*'' is set to true, the child permissions are set to their values defined in the ''plugin.yml''. If however ''doorman.*'' was set to false then all child permissions would be inverted.
+
</syntaxhighlight>
 +
''doorman.*''権限は、いくつかの子権限を含んでいます。
 +
''doorman.*''権限がtrueである場合に、
 +
その子権限は''plugin.yml''に定義された値(trueかfalse)をデフォルト権限として機能します。
 +
''doorman.*''権限がfalseである場合は、
 +
その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。
 +
</blockquote>
  
=== Setting your own permissions  ===
+
ねっ、簡単でしょ?
  
If you wish to know about developing your own permissions plugins (Ones that actaully set permissions) then check out the tutorial on&nbsp;[[Developing a permissions plugin]].
+
=== 独自の権限設定 ===
  
== Scheduling Tasks and Background Tasks  ==
+
独自に権限の仕組みを提供するプラグインを開発したい場合、&nbsp;[[権限プラグインの開発方法]]を参考にして下さい。
  
Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.
+
== スケジューリングタスクとバックグラウンドタスク ==
  
Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.
+
現在、Minecraftサーバはゲームロジックのほとんどがシングルスレッドで稼動しています。<br/>
 +
このため、発生する個々の処理はごく小さいものにする必要があります。<br/>
 +
プラグイン中に複雑なコードが存在して、それが適切に処理されないような場合は、<br/>
 +
ゲームロジックに多大なラグや処理遅延を発生させる原因となります。<br/>
  
There is a separate [[Scheduler Programming]] tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.
+
幸運なことに、Bukkitはプラグインに対してスケジューリングのためのコーディング方法を提供しています。<br/>
 +
どこかのタイミングで一度だけRunnableタスクを実行したり、<br/>
 +
小さなタスクに分けた定期的な繰り返しであったり、<br/>
 +
新規に独立したスレッドを派生させたり・・・<br/>
 +
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。
  
== スケジューリングタスクとバックグラウンドタスク ==
+
詳しくは、[[スケジューラのプログラミング]]中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。
  
 
== ブロックの操作 ==
 
== ブロックの操作 ==
 +
 +
ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。<br/>
 +
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、<br/>
 +
まずはそのブロックを取得した上で、変更を加えることになります。<br/>
 +
PlayerMoveイベントの処理中でこれを行う例を示します:
 +
<blockquote><source lang="java">
 +
@EventHandler
 +
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クラスの仕様]を参照して下さい。
 +
 +
=== エンチャント ===
 +
 +
アイテムにエンチャントを付与するには、ItemStackクラスの '''addEnchantment(Enchantment enchant, int level)''' メソッドを使用します。
  
== HashMapの応用 ==
+
addEnchantment()メソッドでは、元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。<br/>
 +
もし、通常ありえないエンチャントを設定したい場合は、addEnchantment()メソッドの代わりにaddUnsafeEnchantment()メソッドを使ってください。
  
== Map・Set・Listの応用 ==
+
Sharpness 1 エンチャントを石の剣に付与する例を示します。
 +
<blockquote><source lang="java">
 +
// 新しい石の剣を生成します。
 +
ItemStack myItem = new ItemStack(Material.STONE_SWORD);
 +
 
 +
// エンチャントを付与します。
 +
myItem.addEnchantment(Enchantment.DAMAGE_ALL, 1); 
 +
</source></blockquote>
 +
 
 +
次に、火属性 100 を、木の棒に付与する例を示します。
 +
<blockquote><source lang="java">
 +
// 新しい木の棒を生成します。
 +
ItemStack myItem = new ItemStack(Material.STICK);
 +
 
 +
// 木の棒にFireAspectレベル100を付与します。
 +
// ただしFireAspectレベル100は通常存在しないので、addEnchantment を使うと失敗します。
 +
// ありえないエンチャントを設定したい場合は、addUnsafeEnchantment を使ってください。
 +
myItem.addUnsafeEnchantment(Enchantment.FIRE_ASPECT, 100); 
 +
</source></blockquote>
 +
 
 +
=== ItemMeta ===
 +
 
 +
アイテムの表示名を変更するには、次のようにします。
 +
<blockquote><source lang="java">
 +
String myDisplayName = "すごい剣";
 +
 +
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //アイテムを生成します。
 +
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
 +
im.setDisplayName(myDisplayName); //アイテム表示名を設定します。
 +
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。
 +
</source></blockquote>
 +
 
 +
次に、loreを設定してみましょう。loreは、ゲーム内でアイテムにカーソルを合わせたときに表示される説明文のことです。
 +
<blockquote><source lang="java">
 +
List<String> lores = new ArrayList<String>();
 +
lores.add("loreのテストです。");
 +
lores.add("これは2行目です。");
 +
 
 +
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //アイテムを生成します。
 +
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
 +
im.setLore(lores); //loreを設定します。
 +
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。
 +
</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が選択されていることを確認してください。
 +
 +
このチュートリアルで紹介したJDKが同梱のPleiadesを利用している場合で、
 +
<blockquote>
 +
[ERROR] Unable to locate the Javac Compiler in:<br/>
 +
[ERROR] C:\pleiades\java\7\..\lib\tools.jar
 +
</blockquote>
 +
のようなエラーが出ることがあります。これは上記のメッセージの通り、最初から同梱されているJDKにtools.jarが含まれているのに参照設定されていないからです。<br/>
 +
これを解決するには、次のようにします。
 +
*Pleiadesのメニューから、ウィンドウ > 設定 と選び、設定ダイアログを表示します。
 +
*設定ダイアログの左側で、java > インストール済みのJRE と選びます。
 +
*使用しているJRE(先ほどのエラーメッセージに含まれるファイルパスと、ロケーションが、一致するもの)を選択し、「編集...」を押します。
 +
*JREの編集ダイアログで、「外部Jar追加...」を押し、tools.jarを探して選択します。例えばエラーメッセージが「C:\pleiades\java\7\..\lib\tools.jar」なら、「C:\pleiades\java\7\lib\tools.jar」に見つかるはずです。
 +
*「OK」を押して、設定ダイアログを閉じます。
 +
*再度、「mvn install」を実行して、今度は正常にビルドができることを確認してください。
 +
 +
ビルドがうまくいった場合、プロジェクトのフォルダの中に 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 = Bukkit.getPlayerExact(args[0]);
 +
   
 +
        // 対象プレイヤーが、オンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーが見つかりません!");
 +
            return true;
 +
        }
 +
   
 +
        // 対象プレイヤーを、1000tick(=50秒) の間、燃えるようにします。
 +
        target.setFireTicks(1000);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
</source> </blockquote>
 +
 +
=== プレイヤーを殺す ===
 +
 +
同じ要領で、プレイヤーを殺害するコマンドの例を紹介します。
 +
onCommand()メソッドに記述します:
 +
<blockquote><source lang="java">
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
 +
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
 +
        // コマンドのパラメータに、殺害するプレイヤーが指定されているかどうかを
 +
        // 確認します。
 +
        if (args.length != 1) {
 +
            // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを
 +
            // コマンド実行者の画面に表示します。
 +
            return false;
 +
        }
 +
        Player target = Bukkit.getPlayerExact(args[0]);
 +
        // 対象プレイヤーがオンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        // 対象に1000ダメージを与えます。
 +
        target.damage(1000);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
</source></blockquote>
 +
 +
上記の拡張版として、プレイヤーを爆死させる処理を下記に示します:
 +
<blockquote><source lang="java">
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
 +
        Player target = Bukkit.getPlayerExact(args[0]);
 +
        // 対象プレイヤーがオンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        float explosionPower = 4F; // 爆発の大きさです。1Fでガストの火球、3Fでクリーパーの爆発、4FでTNTの爆発 に相当します。
 +
 +
        // 爆発を起こしつつ、対象に1000ダメージを与えます。
 +
        target.getWorld().createExplosion(target.getLocation(), explosionPower);
 +
        target.damage(1000);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
</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;
 +
 +
        // コマンドのパラメータに、対象のプレイヤーが指定されているかどうかを確認します。
 +
        if (args.length != 1) {
 +
            // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを
 +
            // コマンド実行者の画面に表示します。
 +
            return false;
 +
        }
 +
 +
        // 指定されたプレイヤーを取得します。
 +
        // 指定されたプレイヤーがサーバーに接続していない場合、targetはnullになります。
 +
        Player target = Bukkit.getPlayerExact(args[0]);
 +
        if (target == null) {
 +
            sender.sendMessage("Player " + args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。
 +
        target.hidePlayer(s);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
</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>
 +
 +
 +
== プラグインのサンプル兼雛形 ==
 +
* [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''': 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。その点を考慮して必ず原文にも目を通してから質問して下さい。
 +
 +
[[カテゴリ:Bukkitチュートリアル|*]]

2020年1月20日 (月) 12:57時点における最新版

本ページの内容は、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を後から追加インストールすることは可能です。)

spigotなどのBukkitサーバーの実行環境を利用する場合、BuildTools.jar を利用してサーバー実行環境をビルドする必要がありますが、Mavenが利用できる場合は、Mavenが開発用のライブラリを自動でダウンロードするため、BuildTools.jar を使わなくても開発環境を構築することが可能です。
ただし、当然ですが、デバッグ実行するための実行環境は必要になりますから、BuildTools.jar を利用して実行環境も準備しておいてください。このチュートリアルでは、BuildTools.jar を利用した実行環境の構築は割愛いたします。

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 プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?

「ビルドプランを計算できませんでした。」と出た場合、作成したパッケージを右クリックし「実行」から「Maven install」をクリックすることにより、 Mavenのインストールが始まりエラーが出なくなります。

Bukkit API の参照設定[編集]

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

Create project4.png

まず、Java開発環境(JDK)の参照設定をします。
一番最後の行に </project> というタグがありますが、その1行上に、次の内容を挿入してください。
(なお、これは Java 7 を参照してビルドするための設定です。もし Java 6 を参照してビルドしたい場合は、sourceタグとtargetタグに書かれている 1.7 のところを 1.6 に変更してください。また、Java 8を参照したい場合は、1.7を1.8に変更してください。)

  <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 APIを実装しているSpigotのリポジトリがどこにあるかURLで示します。
下記の内容を追記してください。

  <repositories>
    <repository>
      <id>spigot-repo</id>
      <url>https://hub.spigotmc.org/nexus/content/groups/public</url>
    </repository>
  </repositories>

最後に、Bukkit API の参照設定を追加します。
下記の内容を追記してください。

  <dependencies>
    <dependency>
      <groupId>org.bukkit</groupId>
      <artifactId>bukkit</artifactId>
      <version>1.10.2-R0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>

この設定では、Bukkit API 1.10.2-R0.1-SNAPSHOT が参照されます。
別のバージョンを参照したい場合は、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>spigot-repo</id>
      <url>https://hub.spigotmc.org/nexus/content/groups/public</url>
    </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>org.bukkit</groupId>
      <artifactId>bukkit</artifactId>
      <version>1.10.2-R0.1-SNAPSHOT</version>
    </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: メインクラスの設定は、大文字小文字が区別されるので、大文字小文字に注意して設定してください。
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"コマンドを実装している他のプラグインとの互換性は無くなります。<ref group="注">実際には、相互のコマンドを呼び分けるために特定のプレフィクスがコマンドの前に付与されます。しかしそれでも、単にgiveコマンドを呼んだだけではどのコマンドが呼ばれるかわからないことには変わりありません。</ref>

onCommand()は、常にboolean型の値<ref group="注">trueかfalseのどちらか</ref>を戻り値として返さねばなりません。 trueを返した場合は、情報表示のためのイベントは発生しません。 falseを返した場合は、プラグインファイルを"usage: property"に戻し、コマンドを実行したプレイヤーに、コマンドの利用方法を通知するメッセージを表示します。

onCommand()を利用する際は、4つのパラメータを登録しなければなりません。

  • CommandSender sender - コマンドの発行元
  • Command cmd - 実行されたコマンドの内容
  • String commandLabel - 利用されたコマンドエイリアス
  • String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)<ref group="注">半角空白 (U+0020)で分割されて、配列に格納される</ref>

コマンドの設定[編集]

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 (プラグインのメインクラス):

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

MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {

        // メインクラスの参照です。処理の中でメインクラスのメソッドを利用しない場合は、省略して構いません。
	private final MyPlugin instance; 

	public MyPluginCommandExecutor(MyPlugin plugin) {
		this.plugin = plugin;
	}

	@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;
        // ここに、処理を実装する。
        return true;
    } else {
        sender.sendMessage(ChatColor.RED + "ゲーム内から実行してください!");
        return false;
    }
    return false;
}

このように、if (sender instanceof Player) で必ずPlayerであることをチェックしないと、プレイヤーでないコマンド実行者(例えばコンソール)がコマンドを実行したときに、Player player = (Player) sender のところで処理が失敗してしまいます。

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

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インスタンスを利用したい場合、必ずそのプレイヤーがオンラインである必要があります。
オンラインであるかどうかをチェックして下さい。
Bukkit.getPlayer(プレイヤー名) または、Bukkit.getPlayerExact(プレイヤー名) で、オンラインのプレイヤーを取得できます。
(Bukkit.getPlayer はプレイヤー名と前方一致で、Bukkit.getPlayerExact は完全一致で、プレイヤーを取得します。)
もし、指定したプレイヤーがオフラインだった場合は、nullが返されます。
処理例:

@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if (args.length < 1) {
        sender.sendMessage("コマンドの後にプレイヤー名を指定してください!");
        return false;
    }
    Player target = Bukkit.getPlayerExact(args[0]);
    if ( target == null ) {
        sender.sendMessage(ChatColor.RED + args[0] + "さんはオフラインです!");
        return true;
    } else {
        sender.sendMessage(ChatColor.AQUA + args[0] + "さんはオンラインです!");
        return true;
    }
}

オンラインではないプレイヤーのインスタンスに操作を加えたい場合は、一般的にはOfflinePlayerクラスを利用して行う事ができます。

プラグインのConfiguration/Settings[編集]

新しいConfigurationの使い方を参照して下さい

権限[編集]

Note: この節は、日本語読者向けに独自の内容を記述しています。

Bukkitにおける権限の利用は、とっても簡単です!

権限がセットされているか、いない(Javaのnullと同等)かを調べるには以下のメソッドを利用します:

boolean isPermissionSet(String name)

また、権限を持っているかどうかを調べるには以下のメソッドを利用します:

boolean hasPermission(String name)

これを利用してプレイヤーが特定の権限を持っているかどうかを調べる処理は次のようになります:

if(player.hasPermission("some.pointless.permission")) {
   player.sendMessage("あなたは権限を持っています。");
}else{
   player.sendMessage("あなたは権限を持っていません。");
}

2メソッドの違いは、権限がセットされていなくてもデフォルトで与えられていれば権限を持っているという扱いになるかどうかです。
hasPermissionでは デフォルトで与えられていればtrueが返ります。

PermissionsExなどで 権限はグループとして扱われることが多いですが、Bukkitには権限をグループとして扱う機能はありません。
グループとして扱う場合は、別途 上記のようなプラグインを使うか、自分で作る必要があります。

権限の設定[編集]

デフォルトで権限を付与させたり、OPにのみデフォルトで付与させたい場合は、
plugin.ymlを使うと簡単にできます。

下記は、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</source>

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

ねっ、簡単でしょ?

独自の権限設定[編集]

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

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

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

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

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

ブロックの操作[編集]

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

@EventHandler
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()メソッドを使ってください。

Sharpness 1 エンチャントを石の剣に付与する例を示します。

// 新しい石の剣を生成します。
ItemStack myItem = new ItemStack(Material.STONE_SWORD);

// エンチャントを付与します。 
myItem.addEnchantment(Enchantment.DAMAGE_ALL, 1);

次に、火属性 100 を、木の棒に付与する例を示します。

// 新しい木の棒を生成します。
ItemStack myItem = new ItemStack(Material.STICK);

// 木の棒にFireAspectレベル100を付与します。
// ただしFireAspectレベル100は通常存在しないので、addEnchantment を使うと失敗します。
// ありえないエンチャントを設定したい場合は、addUnsafeEnchantment を使ってください。
myItem.addUnsafeEnchantment(Enchantment.FIRE_ASPECT, 100);

ItemMeta[編集]

アイテムの表示名を変更するには、次のようにします。

String myDisplayName = "すごい剣";
 
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //アイテムを生成します。
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
im.setDisplayName(myDisplayName); //アイテム表示名を設定します。
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。

次に、loreを設定してみましょう。loreは、ゲーム内でアイテムにカーソルを合わせたときに表示される説明文のことです。

List<String> lores = new ArrayList<String>();
lores.add("loreのテストです。");
lores.add("これは2行目です。");

ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //アイテムを生成します。
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
im.setLore(lores); //loreを設定します。
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。

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が選択されていることを確認してください。

このチュートリアルで紹介したJDKが同梱のPleiadesを利用している場合で、

[ERROR] Unable to locate the Javac Compiler in:
[ERROR] C:\pleiades\java\7\..\lib\tools.jar

のようなエラーが出ることがあります。これは上記のメッセージの通り、最初から同梱されているJDKにtools.jarが含まれているのに参照設定されていないからです。
これを解決するには、次のようにします。

  • Pleiadesのメニューから、ウィンドウ > 設定 と選び、設定ダイアログを表示します。
  • 設定ダイアログの左側で、java > インストール済みのJRE と選びます。
  • 使用しているJRE(先ほどのエラーメッセージに含まれるファイルパスと、ロケーションが、一致するもの)を選択し、「編集...」を押します。
  • JREの編集ダイアログで、「外部Jar追加...」を押し、tools.jarを探して選択します。例えばエラーメッセージが「C:\pleiades\java\7\..\lib\tools.jar」なら、「C:\pleiades\java\7\lib\tools.jar」に見つかるはずです。
  • 「OK」を押して、設定ダイアログを閉じます。
  • 再度、「mvn install」を実行して、今度は正常にビルドができることを確認してください。

ビルドがうまくいった場合、プロジェクトのフォルダの中に 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 = Bukkit.getPlayerExact(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")){
        // コマンドのパラメータに、殺害するプレイヤーが指定されているかどうかを
        // 確認します。
        if (args.length != 1) {
            // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを
            // コマンド実行者の画面に表示します。
            return false;
        }
        Player target = Bukkit.getPlayerExact(args[0]);
        // 対象プレイヤーがオンラインかどうかを確認します。
        if (target == null) {
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
            return true;
        }
        // 対象に1000ダメージを与えます。
        target.damage(1000);
        return true;
    }
    return false;
}

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

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
        Player target = Bukkit.getPlayerExact(args[0]);
        // 対象プレイヤーがオンラインかどうかを確認します。
        if (target == null) {
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
            return true;
        }
        float explosionPower = 4F; // 爆発の大きさです。1Fでガストの火球、3Fでクリーパーの爆発、4FでTNTの爆発 に相当します。

        // 爆発を起こしつつ、対象に1000ダメージを与えます。
        target.getWorld().createExplosion(target.getLocation(), explosionPower);
        target.damage(1000);
        return true;
    }
    return false;
}

爆発を起こす[編集]

このコードは、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;

        // コマンドのパラメータに、対象のプレイヤーが指定されているかどうかを確認します。
        if (args.length != 1) {
            // onCommandでfalseを戻すと、plugin.ymlのusageに設定したメッセージを
            // コマンド実行者の画面に表示します。
            return false;
        }

        // 指定されたプレイヤーを取得します。
        // 指定されたプレイヤーがサーバーに接続していない場合、targetはnullになります。
        Player target = Bukkit.getPlayerExact(args[0]);
        if (target == null) {
            sender.sendMessage("Player " + args[0] + " というプレイヤーは見つかりません!");
            return true;
        }
        // プレイヤー "s" を、指定したプレイヤー "target" から、非表示に設定します。
        target.hidePlayer(s);
        return true;
    }
    return false;
}

クリックした場所に雷を落とす[編集]

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

@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;
}


プラグインのサンプル兼雛形[編集]

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

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