提供: Minecraft Modding Wiki
2012年6月16日 (土) 22:52時点におけるUdonya (トーク | 投稿記録)による版 (パーミッション)
移動先: 案内検索

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

始めに

このチュートリアルは、こちらのスレッドからの転載です。当ページの原著者は、Adamki11s氏ですが、多くの編集を受けて今に至ります。

Adamki11s Bukkit Profile Page

このチュートリアルを読み終えた後は、Adamki11s'氏の"Extras" からより深い情報を得る事が出来ます。Extras library forums thread.


Note: 当ページには、原文の翻訳ではない、日本語読者向けの独自の記述を行っている箇所が少量あります。そのような内容を含む節ではNoteにて通知しています。
Note: 当ページの内容は、Bukkit用MODの作成方法の内容とは異なるアプローチによるBukkitプラグイン開発手法の解説です。
Note: 当ページでは、技術的な解説やたとえ話の原意を理解し易くするために、一部意訳を行っています。

Javaの習得

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

このチュートリアルの読者には、Java言語プログラミングの基礎がわかる方を対象としています。
Javaの基礎知識が少ない方は、下記の情報に触れてください。

Javaコーディング方法

Javaがどうしても理解できない場合は、この他にも様々な資料があるので探して触れてください。

Javaの開発環境

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

プラグインを開発する(またはJavaを学ぶ)前に、開発環境(IDE (Integrated Development Environment))をインストールして下さい。
開発環境は、プラグインのコンパイルとデバッグを行うために使います。
Javaの主要なIDEには、Eclipse, Netbeans, IntelliJ IDEA の3つがあります。
EclipseはBukkit開発者の中では最も人気があり、IntelliJの方は業界内で広く利用されています。

あなたがJavaの初心者なら、当チュートリアルが解説に利用しているEclipseを利用する事をお勧めします。
Eclipseのお勧めのバージョンの配布元は、日本語 Eclipse / Pleiades All in One 日本語ディストリビューションです。
これはMergedocProjectが配布する拡張されたパッケージであり、Eclipseの一次配布元が提供するパッケージではありません。

どのIDEも気に入らなければ、BlueJも利用できます。
プラグイン開発が初めてであるなら、とにかく一度Eclipseを使ってみて、
良ければNetBeansを試してみると良いでしょう。

優良なEclipseの解説。お勧めのものより古いバージョンの解説ですが、ほぼ同様です。

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

プロジェクトの作成

始める前にEclipseのワークスペースとファイルの設定を行う必要があります。
Eclipseを起動し、ファイル>新規>Java プロジェクトと選択して下さい。

NewJavaProject.png

プロジェクト名を入力し、新規プロジェクトウィザード画面の指示に従って、プロジェクトを作成してください。

Bukkit APIの参照

開発を始める前にbukkit APIライブラリをプロジェクトに追加する必要があります。 使用したい他のAPIも同じように追加することが可能です。

最新のBukkit APIはここからダウンロードが可能です。 Bukkit API - Development Snapshot


画面の左手にあるパッケージエクスプローラの(先ほど名前を付けた)プロジェクトを右クリックし、プロパティーを選択します。
開いた画面のJavaビルド・パスを選択し、ライブラリータブの中から、外部 Jar 追加ボタンを押して、ダウンロードしたBukkit APIを指定します。

BuildPathPic.png

BukkitのJavadocの利用方法

Eclipseを用いたJavaプログラミングの経験がある方なら、
Eclipseのエディタ上でクラスやメソッドにマウスカーソルを重ねた時に、
クラスやメソッドに関するドキュメントがポップアップで表示される事をご存知でしょう。
これは、Oracleのウェブサイトから得る事ができるJavadocの内容が表示されています。

Bukkitもまた、BukkitのAPIが提供するクラスやメソッドの各々のコードに同種のドキュメントを含んでおり、ポップアップで表示させる事ができます。
Eclipse上で、Bukkitのクラスやメソッドにマウスを重ねたタイミングでポップアップを表示できるようにするためには、下記の手順を行います。

  1. プロジェクトエクスプローラ上で、Bukkitのjarファイルを右クリックして、メニューを開く。
  2. "プロパティ"を選択し、表示されるポップアップ左側の"Javadoc ロケーション"項目を選択する。
  3. "Javadoc URL"の下部にあるテキストボックスに、"http://jd.bukkit.org/apidocs/" (ダブルクォートは除く)を貼り付ける。
  4. "検証"ボタンを押下し、URLがJavadocとして正しく識別される事をチェックしてから、OKボタンを押す。
次ような画面になります:
Bukkitjavadocs.png

Plugin開発の開始

MakePackage.png

onEnable() and onDisable()

このファンクションは、プラグインが有効/無効になったときに呼ばれます。
デフォルトでは、プラグインは自動的に読み込まれたときに、イベントを登録やデバッグ出力を行うことが出来ます。
onEnable()は、プラグインがBukkitから読み込まれるときに最初に呼ばれ、プラグインを実行するために必須です。

onEnable()とonDisable()の基本

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

public void onEnable(){ 
 
}
 
public void onDisable(){ 
 
}


現時点では、このメソッドは何も行いません。また、エラーが発生する事にも気付くでしょう。このエラーは、メインクラスが、プラグインの機能を継承(extends)する必要がある事を示しています。当クラスの定義文を、下記のように変更しましょう。

これを・・・

Class <classname> {}

このように変更する。

Class <classname> extends JavaPlugin {}

すると、前述の追加コードが赤の下線でハイライト表示され、何かが間違っていることを通知してくるはずです。このハイライト部にマウスを重ねると、下記のようなポップアップが表示されるので、「'JavaPlugin'をインポートします(org.bukkit.plugin.java)」を選択します。

To need import package.png

もしくは、

import org.bukkit.plugin.java.JavaPlugin;

をコード上部の定義部に記述する事でも同様の事が行えます。

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

始めに、プラグインが実際に動作するかどうかを確認するため、シンプルなメッセージをサーバコンソールに表示させてみましょう。この処理として、ログ出力機能(Logger)をメインクラスに定義して初期化します。

Logger log = this.getLogger();

その後、onEnable()メソッドに、プラグインが動作している事を通知するメッセージを出力する処理を記述します。

log.info("Your plugin has been enabled.");

onDisable()メソッドについても同等の記述を行います。ここまでのコーディングによって、メインクラスは下記の様な内容になっているはずです。

package me.<yourname>.<pluginname>;
 
import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin;
 
public class <classname> extends JavaPlugin {
 
	Logger log;
 
	public void onEnable(){
		log = this.getLogger();
		log.info("Your plugin has been enabled!");
	}
 
	public void onDisable(){
		log.info("Your plugin has been disabled.");
	}
}

イベントとリスナ

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

コマンド

onCommand()メソッド

さて、どのようにイベントを登録しいつ発生するかについて理解しました。 しかし、コマンドを利用して何かを起こしたい場合はどのようなデータ型を利用すればよいのでしょうか?それには、onCommand()メソッドを利用します。 このメソッドは、プレイヤーが文字"/"を入力する度に実行されます。 具体的には、"/do something"とコマンドを実行した場合にonCommand()が呼び出されます。 今のところ、onCommand()はまだプログラムしていないので何も起こらないでしょう。

コマンド名は、Bukkitや他のプラグインが提供しているものや、 提供するであろうものとの重複を避け、 ユニークなものとなるように考慮して下さい。 具体的には、"give"コマンドは既にいくつかのプラグインで利用されています。 もし独自に"give"コマンドを実装した場合は、 "give"コマンドを実装している他のプラグインとの互換性は無くなります。

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

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

  • CommandSender sender - コマンドの発行元
  • Command cmd - 実行されたコマンドの内容
  • String commandLabel - 利用されたコマンドエイリアス
  • String[] args - コマンドの引数を格納した配列(例:/hello abc defコマンドが入力された場合の内容は、args[0]がabc、args[1]がdefとなる)

コマンドの設定

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
        // プレイヤーが「/basic」コマンドを投入した際の処理...
	if(cmd.getName().equalsIgnoreCase("basic")){ 
		// 何かの処理
		return true;
                // コマンドが実行された場合は、trueを返して当メソッドを抜ける。
	}
	return false; 
        // コマンドが実行されなかった場合は、falseを返して当メソッドを抜ける。
}

onCommand()をコーディングする際は、上記の例のように、メソッドの最終ステップにfalseをreturnする行を記述するのが良い方法です。
falseが返る事で、plugin.yml(下記参照)内に記述されたメッセージが表示され、
onCommand()処理が正しく動作しなかった事をメッセージから検知できるため、動作確認の助けになるからです。
逆に、メソッドの最後でtrueを返す処理構造にしてしまった場合は、onCommand()内の各処理に対して、
処理結果チェック処理と結果が不正である場合にfalseを返すような、同じ処理を何度も記述する必要が出てきますので、大変な無駄となります。

なお、コード「.equalsIgnoreCase("basic")」のパラメタ「basic」は、大文字小文字の区別はありません。

plugin.ymlへのコマンドの追加

コマンドを用意する際は、plugin.ymlファイルにも定義を追加する必要があります。plugin.ymlの最後に次の行を追加します。

commands:
   basic:
      description: This is a demo command.
      usage: /<command> [player]
      permission: <plugin name>.basic
      permission-message: You don't have <permission>
  • basic - コマンド名
  • description - コマンドの説明文
  • usage - onCommand()がfalseを返した際に、コマンド実行ユーザに向けて表示されるメッセージの内容。コマンドの使い方を明解に書いてください。
  • permission - 当コマンドの動作に必要なプラグインの設定を、コマンド実行ユーザに知らせるメッセージ。(主に、コマンド実行に必要なpermissionを書きます)
  • permission-message - コマンド実行権限を持たないユーザがコマンドを実行した場合に、その旨を同ユーザに知らせるメッセージ。
Note: ymlファイルには、1個タブを2個のスペースで記述する必要があります。タブ文字は構文エラーとなるため利用できません。

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

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

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

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

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

記述例:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
	Player player = null;
	if (sender instanceof Player) {
		player = (Player) sender;
	}

	if (cmd.getName().equalsIgnoreCase("basic")){ // If the player typed /basic then do the following...
		// do something...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2")) {
		if (player == null) {
			sender.sendMessage("this command can only be run by a player");
		} else {
			// do something else...
		}
		return true;
	}
	return false;
}

この例では、basic コマンドはログインプレイヤーとサーバコンソールのどちらからでも実行できます。しかし、basic2 コマンドは、ログインプレイヤーしか実行できません。 一般的に、コマンドはプレイヤーとサーバコンソールの両者に実行を許可しますが、投入されたコマンドの実行可否をチェックする必要があるコマンドについては、ログインプレイヤー向けのコマンドとして実装されるべきでしょう。

つまり、プレイヤーに依存する処理(テレポートコマンドやアイテムを与えるコマンド等は、対象となるプレイヤーが必要です)を行うコマンドが、プレイヤーコマンドとして実装されるべきものと言えます。

凝ったコマンドを作りたい場合、コマンドのパラメタ(上記例では argsパラメタ)を利用して独自の処理実装を行うことができます。例えば、プレイヤー名を指定したテレポートコマンドのみが、サーバコンソールから実行できる実装にする等が考えられます。

訳者補記: コマンド実装方法には、プレイヤー向け・サーバコンソール向けの2種類があり、それぞれの区別はどのような考え方で行うべきかについて説明しています。

CommandExecutorのクラス分割

上記の例では、onCommand()メソッドをプラグインのメインクラスに記述していました。小さなプラグインでは良い方法ですが、大きなプラグインに拡張していくのであれば、適切なクラスを作成してそちらに配置するべきです。幸い、難しい事ではありません:

  • プラグインのpackage内に、MyPluginCommandExecutor のような名前で新規のコマンド実行クラスを作成する(当然、MyPluginはあなたのプラグイン名に合わせて変えましょう)。
  • MyPluginCommandExecutorに、BukkitのCommandExecutorインタフェースを実装(implements)させる。
  • プラグインのonEnable()メソッドの処理で、MyPluginCommandExecutorクラスのインスタンスを生成する。
  • MyPluginCommandExecutorインスタンスをパラメタとして、処理getCommand("basic").setExecutor(myExecutor);を実行させる。
Note: "basic"は実行されたコマンドであり、myExecutorはMyPluginCommandExecutorインスタンスです。

とっても良い具体例:

MyPlugin.java (the main plugin class):

private MyPluginCommandExecutor myExecutor;
@Override
public void onEnable() {
	// ....
 
	myExecutor = new MyPluginCommandExecutor(this);
	getCommand("basic").setExecutor(myExecutor);
 
	// ...
}
MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {
 
	private MyPlugin plugin;
 
	public MyPluginCommandExecutor(MyPlugin plugin) {
		this.plugin = plugin;
	}
 
	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		// ... implementation exactly as before ...
	}
}

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

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

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

堅牢なonCommandの記述

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

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

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

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if ((sender instanceof Player)) {
           // doSomething
        } else {
           sender.sendMessage(ChatColor.RED + "You must be a player!");
           return false;
        }
        Player player = (Player) sender;
        return false;
}

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

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

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if (args.length > 4) {
           sender.sendMessage(ChatColor.RED + "Too many arguments!");
           return false;
        } 
        if (args.length < 2) {
           sender.sendMessage(ChatColor.RED + "Not enough arguments!");
           return false;
        }
}

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

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

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	Player other = (Bukkit.getServer().getPlayer(args[0]));
        if (other == null) {
           sender.sendMessage(ChatColor.RED + args[0] + " is not online!");
           return false;
        }
        return false;
}

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

プラグインのConfiguration/Settings

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

権限

Bukkitのパーミッション(権限)APIの利用は、簡単ではありません。

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

if(player.hasPermission("some.pointless.permission")) {
   //Do something
}else{
   //Do something else
}

権限がセットされているか、いない(Javaのnullと同等)かを調べる処理は次のようになります:

boolean isPermissionSet(String name)

なぜグループの概念が存在しないかと思った方もいるでしょうが、 そもそも、権限にはグルーピングの概念は不要なのです。

元々、グループの主な用途はチャットメッセージをフォーマッティングする事にありました。
これは権限の機能を利用してもっと簡単に行えます。
例えば、チャットプラグインの設定において、権限とプレフィクスの関連を定義する事が該当します。
具体的には、権限"someChat.prefix.admin"をプレフィクス[Admin]に対応させる定義を行い、
プレイヤーがチャットで発言する度に、プレイヤー名の先頭に[Admin]が付くようになる機能が挙げられます。

他にも、グループに所属する複数のユーザに、
メッセージを送信するような機能を実現するために利用する事が考えられます。
この例を、権限を利用した処理で記述すると以下のようになります:

for(Player player: getServer().getOnlinePlayers()) {

    if(player.hasPermission("send.recieve.message")) {
        player.sendMessage("You were sent a message");
    }

}

さて、依然として
「グループを利用せずに、複数のプレイヤーに権限をセットする良い方法は何なのか?」が疑問かと思いますが・・・
BukkitのAPI自体は、グループの概念を提供していません。
グループの概念を利用するためには、permissionsBukkitのような
グループ権限の機能を提供するプラグインを利用する事になります。
とどのつまり、このAPIはインタフェース(Interface)であり、実装(Implementation)ではないのです。

権限の設定

権限を利用して細かな制御を行いたい場合は、
デフォルト権限と、子権限の設定をplugin.ymlに追記する事を検討して下さい。
この2種類の設定は、オプション(必須ではなく完全に任意で利用される)項目ではありますが、お勧めします。

下記は、plugin.ymlの最後に追加する形で設定する権限です:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false
    doorman.kick:
        description: Allows you to kick a user
        default: op
    doorman.ban:
        description: Allows you to ban a user
        default: op
    doorman.knock:
        description: Knocks on the door!
        default: true
    doorman.denied:
        description: Prevents this user from entering the door


まず、プラグインが利用する各権限を、permissionsノードの子ノードとして定義します。
それぞれの権限はオプションとして、概要(description)、デフォルト値、子ノードを持ちます。

デフォルト権限

プレイヤーが権限を持たない場合、 hasPermission はデフォルトではfalseを返します。
plugin.yml中のデフォルトのノードに対して、下記の4つのどれか1つを設定する事で、
この挙動を変更する事ができます:

  • true - デフォルトで、権限を持つ(=hasPermissionがtrueを返す)。
  • false - デフォルトで、権限を持たない(=hasPermissionがfalseを返す)。
  • op - プレイヤーがOPであれば、権限を持つ(=hasPermissionがtrueを返す)。
  • not op - プレイヤーがOPでなければ、権限を持つ(=hasPermissionがtrueを返す)。

子権限

恐らく今までに、* 権限を利用してサブ権限を割り当てた事があると思います。
これは、変更されたBukkit APIと、子権限の定義によって実現した機能であり、
高い柔軟性を提供しています。
下記はその実装例です:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false

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

独自の権限設定

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

Scheduling Tasks and Background Tasks

Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.

Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.

There is a separate Scheduler Programming tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.

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

ブロックの操作

プレイヤーインベントリの操作

アイテムの操作

HashMapの応用

Map・Set・Listの応用

データベース

プラグインの配布

ヒントとノウハウ