提供: Minecraft Modding Wiki
移動先: 案内検索
(Defining a HashMap)
(<source lang="xml">...</source>の部分を<pre lang="xml">...</pre>に変更(xml内の<source>タグと競合していたため))
 
(11人の利用者による、間の116版が非表示)
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:NewJavaProject.png]]
+
[[Image:create_project.png]]
  
プロジェクト名を入力し、新規プロジェクトウィザード画面の指示に従って、プロジェクトを作成してください。
+
新規プロジェクトのダイアログが開きます。Mavenフォルダを開いて、Mavenプロジェクト を選択し、次へ を押してください。
  
=== Bukkit APIの参照 ===
+
[[Image:create_project1.png]]
開発を始める前にbukkit APIライブラリをプロジェクトに追加する必要があります。
 
使用したい他のAPIも同じように追加することが可能です。
 
  
最新のBukkit APIはここからダウンロードが可能です。 {{BDownload}}
+
次のダイアログで、「シンプルなプロジェクトの作成」にチェックを入れて、次へ を押してください。
  
画面の左手にあるパッケージエクスプローラの(先ほど名前を付けた)プロジェクトを右クリックし、プロパティーを選択します。<br />
+
[[Image:create_project2.png]]
開いた画面のJavaビルド・パスを選択し、ライブラリータブの中から、外部 Jar 追加ボタンを押して、ダウンロードしたBukkit APIを指定します。<br />
 
  
[[Image:BuildPathPic.png|800px]]
+
次のダイアログで、作成するプラグインの「グループID」と「アーティファクトID」を入力します。<br/>
 +
(アーティファクトIDとは、これから作ろうとするプラグインの名前と同義と思っていただいて構いません。)
  
=== BukkitのJavadocの利用方法 ===
+
グループ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>
  
Eclipseを用いたJavaプログラミングの経験がある方なら、<br/>
 
Eclipseのエディタ上でクラスやメソッドにマウスカーソルを重ねた時に、<br/>
 
クラスやメソッドに関するドキュメントがポップアップで表示される事をご存知でしょう。<br/>
 
これは、[http://download.oracle.com/javase/6/docs/api/ Oracleのウェブサイト]から得る事ができるJavadocの内容が表示されています。<br/>
 
  
Bukkitもまた、BukkitのAPIが提供するクラスやメソッドの各々のコードに[http://jd.bukkit.org/apidocs/ 同種のドキュメント]を含んでおり、ポップアップで表示させる事ができます。<br/>
+
下記の名前で始まるような、他者の開発物と重複するパッケージで命名すべきでは'''ありません''':
Eclipse上で、Bukkitのクラスやメソッドにマウスを重ねたタイミングでポップアップを表示できるようにするためには、下記の手順を行います。
+
<blockquote>
 +
* org.bukkit
 +
* net.bukkit
 +
* com.bukkit
 +
* net.minecraft
 +
</blockquote>
  
# プロジェクトエクスプローラ上で、Bukkitのjarファイルを右クリックして、メニューを開く。
+
ベースとなるグループIDを決めたら、次にプラグイン名を決めましょう。ここでは例として、TutorialPlugin と名付けます。
# "プロパティ"を選択し、表示されるポップアップ左側の"Javadoc ロケーション"項目を選択する。
 
# "Javadoc URL"の下部にあるテキストボックスに、"http://jd.bukkit.org/apidocs/" (ダブルクォートは除く)を貼り付ける。
 
# "検証"ボタンを押下し、URLがJavadocとして正しく識別される事をチェックしてから、OKボタンを押す。
 
  
:次ような画面になります:<br/>
+
ダイアログには次のように入力し、完了 を押してください。
:[[Image:Bukkitjavadocs.png]]
 
  
=== Plugin開発の開始 ===
+
[[Image:create_project3.png]]
  
[[Image:MakePackage.png]]
+
Eclipseの画面に戻ると、左側に TutorialPlugin プロジェクトが作成されているはずです。ここまで、うまく作成できましたか?
  
== onEnable() and onDisable() ==
+
「ビルドプランを計算できませんでした。」と出た場合、作成したパッケージを右クリックし「実行」から「Maven install」をクリックすることにより、
このファンクションは、プラグインが有効/無効になったときに呼ばれます。<br />
+
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;
 +
 
 +
import org.bukkit.plugin.java.JavaPlugin;
 +
 
 +
public class TutorialPlugin extends JavaPlugin {
 +
 
 +
}
 +
</pre>
 +
 
 +
{{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行を書いてください。
 +
 
 +
<syntaxhighlight lang="yaml">
 +
name: (あなたのプラグイン名)
 +
main: (作成したパッケージ名).(作成したメインクラス)
 +
version: (あなたのプラグインのバージョン)
 +
</syntaxhighlight>
 +
 
 +
このチュートリアルでは、次のように作成します。
 +
 
 +
<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メソッドが呼び出されたよ!!");
 +
</source></blockquote>
  
<blockquote>[[ファイル:to_need_import_package.png]]</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>クラスを利用して行う事ができます。
367行目: 586行目:
  
 
== 権限 ==
 
== 権限 ==
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>  
  
なぜグループの概念が存在しないかと思った方もいるでしょうが、
+
また、権限を持っているかどうかを調べるには以下のメソッドを利用します:
そもそも、権限にはグルーピングの概念は不要なのです。
+
<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/>
+
2メソッドの違いは、権限がセットされていなくてもデフォルトで与えられていれば権限を持っているという扱いになるかどうかです。<br>
メッセージを送信するような機能を実現するために利用する事が考えられます。<br/>
+
hasPermissionでは デフォルトで与えられていればtrueが返ります。
この例を、権限を利用した処理で記述すると以下のようになります:
 
<blockquote><source lang="java">for(Player player: getServer().getOnlinePlayers()) {
 
  
    if(player.hasPermission("send.recieve.message")) {
+
PermissionsExなどで 権限はグループとして扱われることが多いですが、Bukkitには権限をグループとして扱う機能はありません。<br/>
        player.sendMessage("You were sent a message");
+
グループとして扱う場合は、別途 上記のようなプラグインを使うか、自分で作る必要があります。
    }
 
 
 
}</source> </blockquote>
 
 
 
さて、依然として<br/>
 
「グループを利用せずに、複数のプレイヤーに権限をセットする良い方法は何なのか?」が疑問かと思いますが・・・<br/>
 
BukkitのAPI自体は、グループの概念を提供していません。<br/>
 
グループの概念を利用するためには、permissionsBukkitのような<br/>
 
グループ権限の機能を提供するプラグインを利用する事になります。<br/>
 
とどのつまり、'''このAPIはインタフェース(Interface)であり、実装(Implementation)ではない'''のです。
 
  
 
=== 権限の設定 ===
 
=== 権限の設定 ===
  
権限を利用して細かな制御を行いたい場合は、<br/>
+
デフォルトで権限を付与させたり、OPにのみデフォルトで付与させたい場合は、<br>
デフォルト権限と、子権限の設定を''plugin.yml''に追記する事を検討して下さい。<br/>
+
''plugin.yml''を使うと簡単にできます。
この2種類の設定は、オプション(必須ではなく完全に任意で利用される)項目ではありますが、お勧めします。<br/>
 
  
 
下記は、''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
431行目: 635行目:
 
         default: true
 
         default: true
 
     doorman.denied:
 
     doorman.denied:
         description: Prevents this user from entering the door</source>
+
         description: Prevents this user from entering the door
</code></blockquote>
+
</syntaxhighlight>
 +
</blockquote>
  
  
450行目: 655行目:
 
==== 子権限 ====
 
==== 子権限 ====
  
恐らく今までに、''* 権限''を利用してサブ権限を割り当てた事があると思います。<br/>
+
権限を設定するときに''* 権限''を利用すると、その権限の子権限すべてを操作することができます。<br/>
 
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、<br/>
 
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、<br/>
 
高い柔軟性を提供しています。<br/>
 
高い柔軟性を提供しています。<br/>
 
下記はその実装例です:
 
下記はその実装例です:
<blockquote><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
462行目: 669行目:
 
             doorman.knock: true
 
             doorman.knock: true
 
             doorman.denied: false</source>
 
             doorman.denied: false</source>
 
+
</syntaxhighlight>
 
''doorman.*''権限は、いくつかの子権限を含んでいます。
 
''doorman.*''権限は、いくつかの子権限を含んでいます。
 
''doorman.*''権限がtrueである場合に、
 
''doorman.*''権限がtrueである場合に、
469行目: 676行目:
 
その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。
 
その子権限のデフォルト権限は、全て反転(trueが定義値ならfalseになる)された状態で機能します。
 
</blockquote>  
 
</blockquote>  
 +
 +
ねっ、簡単でしょ?
  
 
=== 独自の権限設定 ===
 
=== 独自の権限設定 ===
487行目: 696行目:
 
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。
 
といった複数の方法で、長大なタスクをゲームロジックと並行で処理させる事ができます。
  
詳しくは、[[スケジューラのプログラミング方法]]中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。
+
詳しくは、[[スケジューラのプログラミング]]中の、同期タスクと非同期タスクのスケジューリング方法に関する解説を参考にして下さい。
  
 
== ブロックの操作 ==
 
== ブロックの操作 ==
 +
 
ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。<br/>
 
ブロックを生成する簡単な方法は、既存のブロックを取得して変更する事です。<br/>
 
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、<br/>
 
例えば、あなたの5ブロック上方にあるブロックを変更したい場合、<br/>
 
まずはそのブロックを取得した上で、変更を加えることになります。<br/>
 
まずはそのブロックを取得した上で、変更を加えることになります。<br/>
 
PlayerMoveイベントの処理中でこれを行う例を示します:
 
PlayerMoveイベントの処理中でこれを行う例を示します:
<blockquote><source lang="java">public void onPlayerMove(PlayerMoveEvent evt) {
+
<blockquote><source lang="java">
Location loc = evt.getPlayer().getLocation(); // プレイヤーからその位置を得る
+
@EventHandler
World w = loc.getWorld(); // 位置からワールドを得る
+
public void onPlayerMove(PlayerMoveEvent evt) {
loc.setY(loc.getY() + 5); // 位置のY座標を+5する
+
    // プレイヤーの位置を取得します。
Block b = w.getBlockAt(loc); // ワールド上の指定位置のブロックを得る
+
    Location loc = event.getPlayer().getLocation();
b.setTypeId(1); // ブロックのタイプIDに1をセットする
+
    // 位置のY座標を+5します。位置情報を変更しているだけで、実際にプレイヤーの位置が移動するわけではないことに注意してください。
 +
    loc.setY(loc.getY() + 5);
 +
    // 指定位置のブロックを取得します。
 +
    Block b = loc.getBlock();
 +
    // ブロックの種類に石(STONE)を設定します。
 +
    b.setType(Material.STONE);
 
}</source></blockquote>  
 
}</source></blockquote>  
  
 
このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、<br/>
 
このコードの処理は、プレイヤーが移動する(=PlayerMoveイベントが発生する)度に、<br/>
プレイヤーの5ブロック上方のブロックがStone(TypeIdが1のブロック)に変化する動作として見えるでしょう。<br/>
+
プレイヤーの5ブロック上方のブロックがStoneに変化する動作として見えるでしょう。<br/>
 
ブロック取得までの流れは・・・
 
ブロック取得までの流れは・・・
 
# 取得したプレイヤーの位置から、ワールドを取得する
 
# 取得したプレイヤーの位置から、ワールドを取得する
 
# 位置のY座標を+5する
 
# 位置のY座標を+5する
# <code>'''w.getBlockAt(loc);'''</code>で、ワールドにおける位置に存在するブロックを得る
+
# <code>'''loc.getBlockAt(loc);'''</code>で、指定位置に存在するブロックを得る
 
となります。<br/>
 
となります。<br/>
  
 
位置(''loc'')中のブロックインスタンス(''b'')に対して、<br/>
 
位置(''loc'')中のブロックインスタンス(''b'')に対して、<br/>
IDを変えたり、ブロックデータ自体を変更する事もできます。
+
Materialを変えたり、ブロックデータ自体を変更する事もできます。
 +
詳しくは、JavaDocを参照してください。<br/>
  
また、ブロックの種類を表すデータはbyte型であるため、<br/>
+
建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う一例を示します。<br/>
byte型にキャストしたデータを与えた指定ができます。<br/>
 
例えば、<code>'''b.setData((byte)3); '''</code>です。
 
 
 
 
 
建物の生成や、一定のアルゴリズムに従ったブロック生成処理などを行う事ができます。<br/>
 
 
例えば、固体ブロックによる立方体を生成する処理は、<br/>
 
例えば、固体ブロックによる立方体を生成する処理は、<br/>
 
3階層にネストしたforループ処理によって記述できます。
 
3階層にネストしたforループ処理によって記述できます。
<blockquote><source lang="java">public void generateCube(Location point, int length){ // public visible method generateCube() with 2 parameters point and location
+
<blockquote><source lang="java">
World world = point.getWorld();
+
    public void generateCube(Location loc, int length){
 
+
        // 与えられたLocationから、立方体の端の座標を取得します。
int x_start = point.getBlockX();     // Set the startpoints to the coordinates of the given location
+
        // getN()メソッドを使うと intへキャストする必要がありますが、
int y_start = point.getBlockY();     // I use getBlockX() instead of getX() because it gives you a int value and so you dont have to cast it with (int)point.getX()
+
        // getBlockN()メソッドを使えばそのままintで座標を取得できます。
int z_start = point.getBlockZ();
+
        int x1 = loc.getBlockX();  
 
+
        int y1 = loc.getBlockY();
int x_lenght = x_start + length;   // now i set the lenghts for each dimension... should be clear.
+
        int z1 = loc.getBlockZ();
int y_lenght = y_start + length;
+
   
int z_lenght = z_start + length;
+
        // 一辺の長さを足すことで、立方体の反対側の座標を計算します。
 
+
        int x2 = x1 + length;
for(int x_operate = x_start; x_operate <= x_length; x_operate++){  
+
        int y2 = y1 + length;
// Loop 1 for the X-Dimension "for x_operate (which is set to x_start)
+
        int z2 = z1 + length;
//do whats inside the loop while x_operate is
+
   
//<= x_length and after each loop increase
+
        World world = loc.getWorld();
//x_operate by 1 (x_operate++ is the same as x_operate=x_operate+1;)
+
   
for(int y_operate = y_start; y_operate <= y_length; y_operate++){// Loop 2 for the Y-Dimension
+
        // x座標方向のループ
for(int z_operate = z_start; z_operate <= z_length; z_operate++){// Loop 3 for the Z-Dimension
+
        for (int xPoint = x1; xPoint <= x2; xPoint++) {  
 
+
            // y座標方向のループ
Block blockToChange = world.getBlockAt(x_operate,y_operate,z_operate); // get the block with the current coordinates
+
            for (int yPoint = y1; yPoint <= y2; yPoint++) {
blockToChange.setTypeId(34);   // set the block to Type 34
+
                // z座標方向のループ
}
+
                for (int zPoint = z1; zPoint <= z2; zPoint++) {
}
+
                    // ループで処理する座標のブロックを取得します。
}
+
                    Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
}</source></blockquote>  
+
                    // ダイアモンドブロックに設定します!
 +
                    currentBlock.setType(Material.DIAMOND_BLOCK);
 +
                }
 +
            }
 +
        }
 +
    }
 +
</source></blockquote>  
  
 
このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。<br/>
 
このメソッドは、辺の長さと開始点の指定を受けて、任意のサイズと位置を持つ直方体を生成する処理です。<br/>
 
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。<br/>
 
ブロックの削除処理の場合も、このロジックを真似て同様のアルゴリズムで実装する事ができます。<br/>
ただし、セットするブロックのIDはゼロ(=air)になりますね。
+
ただし、セットするブロックの種類はMaterial.AIRになりますね。
  
 
== プレイヤーインベントリの操作 ==
 
== プレイヤーインベントリの操作 ==
587行目: 804行目:
 
=== エンチャント ===
 
=== エンチャント ===
  
アイテムに対するエンチャントに触れる前に、[http://ja.minecraftwiki.net/wiki/Data_values Item Code] と [http://ja.minecraftwiki.net/wiki/Enchanting EID]を見てから以下の解説を読んでください。
+
アイテムにエンチャントを付与するには、ItemStackクラスの '''addEnchantment(Enchantment enchant, int level)''' メソッドを使用します。
  
エンチャントは、Enchantmentクラスが受け持っている機能ですが、<br/>
+
addEnchantment()メソッドでは、元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。<br/>
Enchantmentクラス自体は抽象クラスであるため、インスタンス化('''new Enchantment()''')出来ません。
+
もし、通常ありえないエンチャントを設定したい場合は、addEnchantment()メソッドの代わりにaddUnsafeEnchantment()メソッドを使ってください。
エンチャントはEnchantmentWrapperクラスから利用する必要があるからです。
 
  
元々エンチャントが出来ないアイテムに対して、独自にエンチャントを付与する処理は書けません。<br/>
+
Sharpness 1 エンチャントを石の剣に付与する例を示します。
Bukkitサーバ自体が、エンチャント不可能なアイテムに対して、エンチャント情報を付与する仕組みを備えていないからです。<br/>
 
つまり、Fire AspectedなStick(火のエンチャントが付いた木の棒)はあり得ません。
 
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
int itemCode = 280;  // StickのItemID
+
// 新しい石の剣を生成します。
int effectId = 20; // 効果Fire AspectのEID
+
ItemStack myItem = new ItemStack(Material.STONE_SWORD);
int enchantmentLevel = 100; // エンチャントのレベル
 
  
ItemStack myItem = new ItemStack(itemCode);  // 木の棒のインスタンスを生成する
+
// エンチャントを付与します。
Enchantment myEnchantment = new EnchantmentWrapper(effectId);  // エンチャント効果のインスタンスを生成する
+
myItem.addEnchantment(Enchantment.DAMAGE_ALL, 1);   
myItem.addEnchantment(myEnchantment, enchantmentLevel);  // 木の棒にFireAspectを付与する(ただし付与は成功しない)
 
 
</source></blockquote>
 
</source></blockquote>
  
== HashMapの応用 ==
+
次に、火属性 100 を、木の棒に付与する例を示します。
 +
<blockquote><source lang="java">
 +
// 新しい木の棒を生成します。
 +
ItemStack myItem = new ItemStack(Material.STICK);
  
:'''Note''': このセクションは原文が独特な書き回しだったため意訳を行っています。
+
// 木の棒にFireAspectレベル100を付与します。
 +
// ただしFireAspectレベル100は通常存在しないので、addEnchantment を使うと失敗します。
 +
// ありえないエンチャントを設定したい場合は、addUnsafeEnchantment を使ってください。
 +
myItem.addUnsafeEnchantment(Enchantment.FIRE_ASPECT, 100); 
 +
</source></blockquote>
  
複数のプレイヤーが発するアクションやイベントを処理するためには、<br/>
+
=== ItemMeta ===
イベントの発生や状態を保持するための領域として単一の変数を利用していたのでは不十分です。
 
  
例を挙げると、<br/>
+
アイテムの表示名を変更するには、次のようにします。
私が作った古いプラグインのZones(今はRegionsに改名して処理も改善しています)では、<br/>
+
<blockquote><source lang="java">
プラグインがサーバ上で実際にどのように振舞うかに関して考慮し切れていなかったために、<br/>
+
String myDisplayName = "すごい剣";
多くのエラーに直面しました。<br/>
+
具体的には、プレイヤーが特定の領域内に居たかどうかをチェックする処理で<br/>
+
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //アイテムを生成します。
チェック結果の格納先として単一のboolean値を利用したため、<br/>
+
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
複数のプレイヤーそれぞれに対するチェックを個別に保持する事が出来ずに膨大なエラーを引き起こしたのです。
+
im.setDisplayName(myDisplayName); //アイテム表示名を設定します。
 +
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。
 +
</source></blockquote>
  
HashMapはこのような問題を解決する素晴らしい方法です。<br/>
+
次に、loreを設定してみましょう。loreは、ゲーム内でアイテムにカーソルを合わせたときに表示される説明文のことです。
HashMapは、データを「'''キー'''と'''値'''のペア」の集まりとして保管できるデータ保持機能です。<br/>
+
<blockquote><source lang="java">
これを利用するメリットは、HashMapが下記の仕様を備えている事です。<br/>
+
List<String> lores = new ArrayList<String>();
* 1つのキーに対して、必ず1つの値が対応する
+
lores.add("loreのテストです。");
* 同じ内容のキーが、同一のインスタンス内に重複して存在できない
+
lores.add("これは2行目です。");
つまり、キーがプレイヤー、値がプレイヤーに紐付く情報の保管先になるようにHashMapを利用する事で、<br/>
 
プレイヤーとプレイヤーに紐付く値が強制的にペア(対)で管理できますし、<br/>
 
プレイヤーがインスタンス内で重複し得ない(ゲーム内で同一のプレイヤーは重複し得ない)事を保証できるのです。
 
  
具体的には、"TaroYamada"がキーとなり、"False"が値となります。<br/>
+
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD); //アイテムを生成します。
また、後からキー"TaroYamada"に対応する値を"True"に変更する事もできます:
+
ItemMeta im = myItem.getItemMeta(); //ItemStackから、ItemMetaを取得します。
<blockquote><source lang="java">
+
im.setLore(lores); //loreを設定します。
import java.util.HashMap;
+
myItem.setItemMeta(im); //元のItemStackに、変更したItemMetaを設定します。
public class Sample{
 
  public Sample(){
 
    HashMap<String, Boolean> playerFlags = new HashMap<String, Boolean>();
 
    playerFlags.put("TaroYamada", false);
 
    playerFlags.put("TaroYamada", true);
 
    playerFlags.put("GorillaMatsui", true);
 
  }
 
}
 
 
</source></blockquote>
 
</source></blockquote>
:'''Note''': 理解のために独自に追加したコードです。
 
  
=== Defining a HashMap  ===
+
== Metadata ==
<blockquote><source lang="java">public Map<Key, DataType> HashMapName = new HashMap<Key, Datatype>(); //Example syntax
+
 
 +
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を使うデメリット ===
  
//Example Declaration
+
* データの取得・設定を行うときに、ひと手間が必要になります。
 +
* Bukkitが停止すると、全てのMetadataが消えます。
  
public Map<Player, Boolean> pluginEnabled = new HashMap<Player, Boolean>();
+
=== Metadataの使い方 ===
public Map<Player, Boolean> isGodMode = new HashMap<Player, Boolean>();
 
</source> </blockquote>
 
  
このコードは、以降のHashMapsの説明で使うので覚えてください。
+
<source lang="java">
さて、例としてプラグインが有効・無効化する時に、天候を切り替える簡単な処理を作りましょう。
 
まずはonCommand()の中に、前述の説明と同様にプレイヤーの状態に応じて、プレイヤー名を送信する処理を記述します。
 
  
onCommand()には下記を記述します。
+
    /**
読んでいるメソッドの名前は変えても良いですが、意味が通じるシンプルなものにして下さい。
+
    * PlayerにMetadataを設定するサンプルメソッドです。
<blockquote><source lang="java">Player player = (Player) sender;
+
    * @param player 対象プレイヤー
togglePluginState(player);</source> </blockquote>
+
    * @param key Metadataのキー名
上記のコードは、senderをPlayer型にキャストしたものをパラメタとして'''togglePluginState()'''に与えています。
+
    * @param value Metadataの値
では、'''togglePluginState()'''を実装しましょう。
+
    * @param plugin プラグインクラス
<blockquote><source lang="java">public void togglePluginState(Player player){
+
    */
   
+
    public void setMetadata(Player player, String key, Object value, Plugin plugin) {
    if(pluginEnabled.containsKey(player)){
+
        player.setMetadata(key, new FixedMetadataValue(plugin, value));
        if(pluginEnabled.get(player)){
+
    }
            pluginEnabled.put(player, false);
+
 
            player.sendMessage("Plugin disabled");
+
    /**
        } else {
+
    * PlayerからMetadataを取得するサンプルメソッドです。
            pluginEnabled.put(player, true);
+
    * @param player 対象プレイヤー
             player.sendMessage("Plugin enabled");
+
    * @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();
 +
             }
 
         }
 
         }
    } else {
+
         return null;
         pluginEnabled.put(player, true); //If you want plugin enabled by default change this value to false.
 
        player.sendMessage("Plugin enabled");
 
 
     }
 
     }
 +
</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/>
 +
もしビルドが失敗したなら、エラー情報を元に、エラーの解決を試みてください。
  
}</source> </blockquote>  
+
このチュートリアルで紹介したJDKが同梱のPleiadesを利用していない場合、MavenがJDKを見つけられずにエラーになっていることが多いです。<br/>
 +
その場合は、Eclipseの設定を開いて、正しいJDKを選択しなおしてください。<br/>
 +
Eclipseの設定は、「ウィンドウ」メニュー>設定 を選択し、開いたダイアログで、Java>インストール済みのJRE を選択します。<br/>
 +
ここで、正しいバージョンのJDKが選択されていることを確認してください。
  
このコードが何をやっているかというと、
+
このチュートリアルで紹介したJDKが同梱のPleiadesを利用している場合で、
まず、HashMapの'''pluginEnabled'''が'''player'''をキーとして持っているかと、
+
<blockquote>
次に、値が'''true'''と'''false'''のどちらなのかを調べています。
+
[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」を実行して、今度は正常にビルドができることを確認してください。
  
playerがキーに含まれており、かつ
+
ビルドがうまくいった場合、プロジェクトのフォルダの中に target フォルダが作成されており、そのフォルダの中にビルドされたJarファイルがあります。
値がtrueであれば、falseにリセットしてプレイヤーへメッセージを送信します。
 
falseであれば、trueにリセットして同様にメッセージを送信します。
 
  
playerがキーに含まれていない場合、
+
[[Image:Plugin_deploy2.png]]
今処理を'''player'''が初めて行った当処理の実行とみなして、
 
HashMapへプレイヤーと値のペアを追加し、メッセージを送信します。
 
  
=== More Ideas for HashMaps  ===
 
  
A HashMap (or really any kind of Map in Java) is an association. It allows quick and efficient lookup of some sort of '''value''', given a unique '''key'''. Anywhere this happens in your code, a Map may be your solution.
+
プラグインのコードとplugin.ymlに不備が無ければ、エクスポートしたJarファイルはすぐにBukkitプラグインとして動作します。Jarファイルを、Bukkitサーバの'''"plugins"'''フォルダの中に配置し、Bukkitサーバを起動し、プラグインの動作確認をしてみましょう。なお、ローカルマシン上で起動するBukkitサーバへは、Minecraftクライアントのマルチプレイヤーサーバの接続先IPアドレスに'''"localhost"'''を指定して接続する事でログインできます。
  
Here are a few other ideas which are ideally suited to using Maps. As you will see, it doesn't have to be data that you store per player, but can be any kind of data that needs to be "translated" from one form to another.  
+
もしプラグインが上手く動かず、それがどうしても自分で解決できない場合は、当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ユーザ・開発者に貢献する事ができます。
  
==== Data Value Lookups  ====
+
== ヒントとノウハウ ==
 +
CraftBukkit APIは、すばらしい機能をたくさん提供しています。
 +
下記に、面白い効果を実現するコードを示します。
  
 +
=== プレイヤーに着火する ===
 +
下記は、指定されたプレイヤーに着火するサンプルです。例えば '''/ignite Notch''' と実行すると、Notchが燃えます!
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
public Map<String, Integer> wool_colors = new HashMap<String, Integer>();
+
@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>
  
// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
+
上記の拡張版として、プレイヤーを爆死させる処理を下記に示します:  
wool_colors("orange", 1);
+
<blockquote><source lang="java">
wool_colors("magenta", 2);
+
@Override
wool_colors("light blue", 3);
+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
  ...
+
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
wool_colors("black", 15);
+
        Player target = Bukkit.getPlayerExact(args[0]);
 +
        // 対象プレイヤーがオンラインかどうかを確認します。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " というプレイヤーは見つかりません!");
 +
            return true;
 +
        }
 +
        float explosionPower = 4F; // 爆発の大きさです。1Fでガストの火球、3Fでクリーパーの爆発、4FでTNTの爆発 に相当します。
  
// Run this in response to user commands - turn "green" into 13
+
        // 爆発を起こしつつ、対象に1000ダメージを与えます。
int datavalue = 0;
+
        target.getWorld().createExplosion(target.getLocation(), explosionPower);
if (wool_colors.containsKey(argument)
+
        target.damage(1000);
    datavalue = wool_colors.get(argument);
+
        return true;
else {
+
     }
     try { datavalue = Integer.parseInt(argument); }
+
     return false;
     catch (Exception e) { ; }
 
 
}
 
}
 
</source> </blockquote>
 
</source> </blockquote>
 
=== Saving/Loading a HashMap  ===
 
  
Once you know how to work with HashMaps, you probably want to know how to save and load the HashMap data. Saving and loading HashMap data is appropriate if  
+
=== 爆発を起こす ===
 +
このコードは、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);
  
*you don't want an administrator to edit the data manually
+
    // 手前側から検証を行う。
 +
    // Blockが取得できた時点でreturnして終了する。
 +
    while ( it.hasNext() ) {
  
*you need to save data in binary format (too complex to organize for YAML)
+
        Block block = it.next();
  
*you want to avoid parsing block names and/or other objects from freeform text
+
        if ( block.getType() != Material.AIR ) {
 +
            // ブロックが見つかった
 +
            return block;
 +
        }
 +
    }
  
This is very simple way how to save any HashMap. You can replace HashMap&lt;Player,Boolean&gt; with any type of HashMap you want. Let's continue using the "pluginEnabled" HashMap defined from the previous tutorial. This code saves the given HashMap to the file with given path.
+
    // 最後までブロックがみつからなかった
<blockquote><source lang="java">public void save(HashMap<Player,Boolean> pluginEnabled, String path)
+
    return null;
{
+
}
try{
+
</source>
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
+
</blockquote>
oos.writeObject(pluginEnabled);
 
oos.flush();
 
oos.close();
 
//Handle I/O exceptions
 
}catch(Exception e){
 
e.printStackTrace();
 
}
 
}</source> </blockquote>
 
You can see it's really easy. Loading works very very similar but we use ObjectInputStream instead of ObjectOutputStream ,FileInputStream instead of FileOutputStream,readObject() instead of writeObject() and we return the HashMap.
 
<blockquote><source lang="java">public HashMap<Player,Boolean> load(String path) {
 
try{
 
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
 
Object result = ois.readObject();
 
//you can feel free to cast result to HashMap<Player,Boolean> if you know there's that HashMap in the file
 
return (HashMap<Player,Boolean>)result;
 
}catch(Exception e){
 
e.printStackTrace();
 
}
 
}</source> </blockquote>
 
You can use this "API" for saving/loading HashMaps, ArrayLists, Blocks, Players... and all Objects you know&nbsp;;) . Please credit me (Tomsik68) if you use this in your plugin.
 
<blockquote><source lang="java">/** SLAPI = Saving/Loading API
 
* API for Saving and Loading Objects.
 
* @author Tomsik68
 
*/
 
public class SLAPI
 
{
 
public static void save(Object obj,String path) throws Exception
 
{
 
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
 
oos.writeObject(obj);
 
oos.flush();
 
oos.close();
 
}
 
public static Object load(String path) throws Exception
 
{
 
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
 
Object result = ois.readObject();
 
ois.close();
 
return result;
 
}
 
}</source> </blockquote>
 
Example implementation of this API: '''I'm skipping some part of code in this source'''
 
<blockquote><source lang="java">public class Example extends JavaPlugin {
 
private ArrayList<Object> list = new ArrayList<Object>();
 
public void onEnable()
 
{
 
list = (ArrayList<Object>)SLAPI.load("example.bin");
 
}
 
public void onDisable()
 
{
 
SLAPI.save(list,"example.bin");
 
}
 
}</source> </blockquote>  
 
A minor note about this SLAPI and Java's ObjectOutputStream class. This will work un-modified if you are saving almost all well-known Java types like Integer, String, HashMap. This will work un-modified for some Bukkit types as well. If you're writing your own data object classes, and you may want to save their state using this technique, you should read up on Java's Serializable interface. It's easy to add to your code, and it will make your data persistent with very little work on your part. No more parsing!
 
  
== Map・Set・Listの応用 ==
 
  
== データベース ==
+
== プラグインのサンプル兼雛形 ==
 +
* [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: 当ページは訳文であるため、訳文自体に対する文責は原著者にありません。その点を考慮して必ず原文にも目を通してから質問して下さい。