第6章 マインクラフトのサーバー管理を楽で便利なものにしてみる話

74回生 日野

6.1 誤植訂正

部誌サンプルのタイトルが「Mineraftのサーバー管理を楽で便利なものにしてみる話」になっていますが、正しくは「マインクラフトのサーバー管理を楽で便利なものにしてみる話」です。お詫びして訂正いたします。

6.2 初めに&自己紹介

初めまして。74回の日野と申します。この度は第72回灘校文化祭・NPCAにお越しいただきありがとうございます。今回の部誌では元あるゲームを改造してみる、ということに関して部誌を書かせてもらいます。

改造するゲームは「マインクラフト」ってやつです。有名ですよね。中一の一年間これに溶かしました。成績が悲惨なことになるので熱中しすぎないようにしましょう。

で、本題の「どのように改造するのか」ですが、マインクラフトのサーバー管理に使える「プラグイン」というやつを作ってみます。(Mod作成だと思った人、ごめんなさい。どうあがいてもまだ作れそうにありません。)作るバージョンは1.11.2で行きます。

6.3 プラグインって?

いきなりプラグインの話をしても戸惑う人もいるかもしれないので、ブラウザを例にとって説明してみます。

この部誌を手に取ってくれる人はたぶん「Firefox」というウェブブラウザーについて知っていると思います。このブラウザを使う人は多くいます。じゃあなぜこんなに多くの需要があるのか。答えは簡単で、拡張性が高いからです。(拡張機能自体はどのブラウザにも多少は存在するのですが、一番高いのがFirefoxです。)きっと拡張性がないFirefoxは誰からも使われないでしょう。

で、どのように拡張できるのかというと、

  • ダウンロードの管理
  • マウスジェスチャーでショートカット
  • Webページから直接動画をダウンロードする

のように、様々です。たぶん僕が知らないだけで他にもいっぱいあるんでしょう。詳細はあんまり書きません。

そしてこの拡張機能を提供してくれるのがプラグインなのです。今はFirefoxを例にとって言いましたが、このように拡張性が高いものはブラウザだけではありません。たとえば、プログラムを書く環境であったり、動画や画像の編集ソフトがあったりします。ちなみに今自分はこの部誌をAtomというエディタで書いてるんですが、これも拡張機能つけて便利にしています。そして、一部のゲームはこれらと同様に拡張性が高いのです(拡張性が高いほかのゲームに関してはグーグル先生に聞いてみてください。たぶんいっぱいある)。基本的にシングルプレイメインのゲームは拡張性高いです。

6.4 実際に作ってみる前の下準備

まずは作成に必要な下準備です。

開発環境のDL

個人的にはEclipceがおすすめです。ここから有志のみなさんが作ってくれた日本語化プラグインつきでEclipceがインストールできます。(プラグインについては分かりますね?割といろいろなところで使われてます。)特に問題がなければ最新バージョンをインストールするのでOKです。

マインクラフトのアカウントを用意する

こればっかりはストアで買ってください。間違ってもWindows10版を買わないようにね。最近円に対応して3000円固定になりました。ここで購入できます。

マインクラフトのサーバーを用意する

ググってください。解説してるサイトはたくさんあります。解説してると量が膨大に...

6.5 「何もしない」プラグインを作ってみる

まずはプロジェクトを作成します。ファイル→新規→プロジェクトを選択してください。javaプロジェクトを選択して、「次へ」をクリックします。プロジェクト名を設定して、完了をクリックしてください。ここではプロジェクト名をFirstPluginにします。

次に、マインクラフトサーバーのプラグインを作成するのに必要なjavaのビルドパスを通します。次に、パッケージ・エクスプローラーの中のFirstPluginを右クリックして、プロパティー→Javaのビルドパス→外部jarの選択をクリックします。出てきたところで使用するバージョン(ここでは1.11.2)のspigotサーバーを選択します。写真のようになってたら成功です。

このように表示されていることを確認してください。

図6.1: このように表示されていることを確認してください。

javaのビルドパスを通せたら、パッケージを作成します。ツリーを開いて、scrのフォルダを右クリックして、新規作成→パッケージを選択してください。パッケージの名前をfirst.pluginにします。

次に、ソースコードを書く場所を作成します。さっき作ったパッケージを右クリックして、新規作成→クラスを選択します。名前はここではFirstPluginとします。ここで、スーパークラスの参照をクリックします。出てきたウィンドウのボックスにJavaPluginと入力して、でてきたリストの中のJavaPlugin-org.bukkit.plugin.javaを選択してOKをクリックします。

すると、画像のようにデフォルトでコードがいくつか生成されます。

こんな感じです

図6.2: こんな感じです

次に、サーバー起動時と終了時の処理を実装します。ソース→メソッドのオーバーライド/実装を選択し、開いた画面からonDisable()onEnable()を選択して、OKを押します。入力されたのがわかりましたか?

onDisableとonEnableを実装したところ

図6.3: onDisableとonEnableを実装したところ

これで何も機能を持たないプラグインが完成しました。

はい。簡単でしょ?

ちなみに、追加されたonDiable()とonEnable()はメソッドと呼ばれます。詳しくはググって。

ただこれだけでは動作しないので、プラグインの仕様書を作る必要があります。プロジェクトエクスプローラーのFirstPluginを右クリックして、新規→ファイルを選択します。ファイル名にplugin.ymlと入力して完了しましょう。テキストエディタが展開します。そうしたら、plugin.ymlの中に

  name: FirstPlugin
  version: 1.0.0
  main: first.plugin.FirstPlugin

と入力します。ここで間違えると動かないので注意しましょう。 plugin.ymlの中身についてはあとで解説してます。

イメージ

図6.4: イメージ

こんな感じです。

次にプログラムをコンパイルします。FirstPluginを右クリック→エクスポートを選択してください。JARファイルを選択して次へをクリックします。

JARファイルを選択

図6.5: JARファイルを選択

次に、この画面で.classpathと.projectからチェックを外して、plugin.ymlにチェックを入れてください。エクスポートは用意しているサーバーのpluginフォルダに直接してしまいましょう。名前はFirstPlugin.jarにします。この時、エクスポート先にFirstPlugin.jarまで書くようにしてください。画像を参考にしてくださいね。

エクスポート設定

図6.6: エクスポート設定

エクスポートできたらサーバーを起動して、plコマンドを実行してみましょう。緑色でFirstPluginと表示されていたら成功です!

6.6 コマンドを作る

マインクラフトをプレイしたことがある人なら、一度はマルチプレイのサーバーにつないだことがあると思います。その時に、サーバー独自のコマンドを使ったことはありませんか?実はそのコマンドたちも、プラグインによって実装されています。ここでは、コマンドを実装してみます。

クラスの実装

クラスとは?

マインクラフトのプラグインを作ってく上で、クラスの概念は重要です。マインクラフトのプラグインでは、コマンドや動作ごとに行う処理一つ一つをそれぞれのクラスを作成して行っています。下の画像のようなイメージです。

クラスのイメージ画像

図6.7: クラスのイメージ画像

クラスの説明が終わったところでクラスを実装してみます。まずは、onEnableメソッドにgetCommand("first").setExecutor(new FirstCommand());と入力してください。これは、「firstってコマンドが入力されたらFirstcommandってクラスで対応してね」という意味です。すると、new FirstCommandに赤線が引かれると思います。これは、「FirstCommandってなんやねんワイ知らんわ」という意味です。なので、FirstCommandというクラスを作成する必要があります。その赤線にマウスを合わせて、クラスを作成を押してください。出てきた画面では設定を変えずに作成してください。これで、firstというコマンドを処理する専用のクラスができました。

因みに、getCommandのsetExcutorの部分をthisに設定すると、getCommandを記述したクラスで処理することができます。が、クラスのインポートが必要になりますし、何より見た目が汚くなります。お勧めしません。thisに設定する時には、public class FirstPlugin extends JavaPluginpublic class FirstPlugin extends JavaPlugin implements CommandExecutorに手動で変更してください。クラスを新規作成した時には自動で追加されるので問題ありません。

onCommandメソッドの役割

さて、クラスを生成することはできましたが、この自動で入力されたonCommandとは何でしょうか?その役割を見ていきましょう。ちなみにこれもメソッドですよ~。

onCommandメソッドの引数

onCommandメソッドの引数を見てみるとsender,command,label,args[]の4種類が存在します。一つずつ紹介していきます。

sender
コマンドを使った人の情報が入ります。使うにはPlayer型にキャストしなければいけません。
command
コマンドの情報が入ります。基本使わないかな?
labelとargs[]
/test a b cというコマンドがあったとします。この時、labelにaが代入されて、args[0]にb、args[1]にcが代入されます。これでわかるよね?

因みに、クラス作成した直後はarg0,arg1,arg2,arg3[]になっています。上で紹介したsender,command,label,args[]のほうが簡単なので、書き換えてください。この部誌の中ではsender,command,label,args[]で説明します。

作成したすぐのクラス。変数名をそれぞれ変更することを推奨します。

図6.8: 作成したすぐのクラス。変数名をそれぞれ変更することを推奨します。

onCommandメソッドの返り値

onCommandメソッドの返り値はboolean型です。trueの時には特に何も起こりませんが、falseの時にはコマンドを送った人に、あとで紹介するplugin.ymlで設定したメッセージを送信します。

onCommandで何か処理してみよう

せっかくクラスとメソッドを作ったのに何もしないのはもったいないので、何か処理を実装してみましょう。ここでは、インベントリをダイヤブロックで満たしてみたいと思います。

ここで、コマンドの送信者について考えてみましょう。もし、このコマンドがコンソールから実行されていたらどうなると思いますか?当然コンソールにアイテムを渡すことができずに、処理に失敗してしまします。無理やり実行するとこのようにエラーが発生します。

consoleに無理やりアイテムを渡そうとしたらこうなる

図6.9: consoleに無理やりアイテムを渡そうとしたらこうなる

なので、コマンドを実行した人にアイテムを渡す前に、コマンドを実行したのがコンソールかプレイヤーかを確認する必要があります。Javaで、変数の型を比較するには"instanceof"を使用します。senderがPlayer型の変数ではなかったときはsenderがコンソールなので、return falseで終了させてしまいましょう。実際に書くコードはとなります。

次に、コマンドを送信したプレイヤーの情報を取得して、Player型にキャストします。Player playerで、プレイヤー情報を保存する変数を作成できます。次に、プレイヤー型にキャストしたsenderの情報をplayerに代入します。player = (Player)senderでプレイヤー型にキャストすることができます。

ここで、あれ?と思った人が多いと思います。なんたって、Playerの下に赤線が引かれてますから。これは、「Player型ってなんやねんワイ知らんぞ」とコンパイラが言っているのです。なので、Player型を処理するパッケージをインポートする必要があります。Ctrl+Shift+Oを押してください。赤線がなくなったらOKです。

次にプレイヤーに渡すアイテムを表す変数を作成します。。まず、ItemStack型の変数itemを作成します。このItemStack型もコンパイラは知らないので、Ctrl+Shift+OでItemStack型をインポートしてください。この時、2つの選択肢を提示されますが、org.bukkit...を選択してください。

こっちを選択してください

図6.10: こっちを選択してください

次に、itemにダイヤモンドというデータを代入します。アイテムデータはItemStackを使用することで取得できます。あとこのnewをくっつけることでインスタンスの生成をすることができます。インスタンスの生成に関してはググってください。実装すると、下のようになります。

ダイヤモンドのデータを持つ変数の作成

ItemStack item;
item = new ItemStack(Material.DIAMOND);

次に、コマンドの実行者のインベントリを取得します。。Inventory型の変数invを作成して、playerのインベントリを保存します。またいつものように赤線が引かれるのでインポートしてください。次に、送信者のインベントリデータをinvに代入します。playerのインベントリデータはplayer.getInventory()で取得できます。

インベントリの取得

Inventory inv ;
inv = player.getInventory();

最後に、インベントリにアイテムを追加する処理です。インベントリにアイテムを追加する処理はaddItem()setItem()の2種類が存在します。addItemはプレイヤーのインベントリに場所を指定せずにアイテムを追加します。アイテムを投げられる、とするとイメージがわきやすいと思います。それに対して、setItemではインベントリの指定した場所にアイテムを追加します。もとからあったアイテムは上書きされます。今回は、インベントリをダイヤブロックで満たすので、setItemを使用します。setItemの書式はsetItem(インベントリ番号,アイテム)です。インベントリ番号は、インベントリスロット一つ一つに割り当てられています。割り当てのイメージは下の画像のような感じです。

番号割り振りのイメージ ダイヤブロックの数が番号を表す

図6.11: 番号割り振りのイメージ ダイヤブロックの数が番号を表す

今回は、0~35のスロットにダイヤモンドをセットします。36回同じ処理を書くのは面倒なので、For文を使ってみましょう。

プレイヤーにアイテムを渡す処理は以下のようになります。

プレイヤーにアイテムを渡す

for(int i=0;i<36;i++){
    inv.setItem(i,item);
}

最後に、コマンドを実行したプレイヤーにメッセージを送信してみる機能を実装してみましょう。(プレイヤー型の変数).sendMessage("メッセージの内容")で、変数で指定されたプレイヤーにメッセージを送ることができます。playerにキャストして代入したのなら、player.sendMessage("メッセージ内容")で送信できます。

さあ、これでコマンドが完成しました。

ここまで

図6.12: ここまで

変数の初期化

上に示したコードでは変数を作成した後に代入していますが、作成するのと同時に初期化してもかまいません。例えばPlayer型の変数を作成するときにPlayer player = (Player)senderのようにすることができます。これより後ではこの方法を使っている前提で説明します。====[/column]

コマンド作成時に必要なplugin.ymlへの記載

ただ、これだけだとコマンドは実行されません。Enable時にplugin.ymlを通じてコマンドの設定をサーバーに読み込ませる必要があります。

まず、plugin.ymlを開きます。開いたら、次の画像のように入力してください。commands:の後が追加されています。空白の部分はすべて半角スペース2つで開けてください。タブではないです。

plugin.ymlのイメージ

図6.13: plugin.ymlのイメージ

plugin.ymlの役目とそれぞれの役割

plugin.ymlは、サーバーにプラグインの情報を伝える役目をしています。それぞれの設定のセットの役割を確認してみましょう。

サーバーの基本設定

name
必須。プラグインの名前です。plコマンドを実行したときに表示されたり、pluginフォルダの下に生成されるフォルダ名に適用されたりします。jarファイルの名前と揃えることが推奨。
version
必須。/versino <プラグイン名>が実行されたときに表示されます。バージョン管理番号です。任意の文字列が使用可能です。外部に公開したりするときには真面目に設定したほうがいいです。まあ個人で使う分には0.0.0とか1.0.0でいいでしょう。
main
必須。プラグインのメインクラスの場所を記述します。パッケージ名を含めて指定してください。
description
/version <プラグイン名>が実行されたときに表示されます。必須ではないですが、説明なのでつけておく価値はあります。
commands
プラグインによって追加されるコマンドの設定を記述します。

commandsの設定

<コマンド名>
コマンド名を記述します。コマンドはスラッシュ(/)を省いた状態で記述してください。
description
コマンドの説明文です。helpコマンドを実行したときに表示されます。
usage
onCommandメソッドがfalseを返した時に表示されます。<command>を使うと、実際にメッセージが表示されたときに<command>の部分が初めに設定したコマンド名に置き換わります。
default
権限を設定します。permissionでも同じことをしてくれますが、こちらはpermissionの簡易版です。defaultの後にtrue(全員が使用可能),false(全員が使用不可能),op(OPのみ使用可能),notop(OPのみが使用不可能)を指定することができます。

permissionの設定周りはここでは扱いません。探したらわかりやすいサイトがたくさん出てくると思います。

plugin.ymlの編集が終わったら、前回と同じようにしてエクスポートしてください。上書きしてもらって構いません。

サーバーを起動して、plコマンドを実行して、表示されることを確認してください。プラグインが表示されたら、マインクラフトを起動してサーバーにログインして、コマンドを実行してみてください。

実行結果

図6.14: 実行結果

インベントリがダイヤブロックで満たされたら成功です!

6.7 configファイルを作ってみる

マインクラフトのプラグインを使ったことがある人は、コンフィグファイルを使ったことがあると思います。ここでは、コンフィグ周りの設定方法を解説したいと思います。

config.ymlの作成

まず最初に元となるコンフィグを作成したいと思います。plugin.ymlと同じようにしてconfig.ymlを作成してしまいましょう。中身はここでは画像のようにしておきます。

config.ymlの中身はこのようにしておきます。

図6.15: config.ymlの中身はこのようにしておきます。

config.ymlをサーバーフォルダに保存させる

configファイルは起動時に読み込んでおきたいので、onEnableメソッドに記述します。

plugin/<プラグイン名>のフォルダにconfig.ymlを保存させるには、saveDefaultConfig();を使います。この時、このクラスで処理することを明示するためにthisを付けてthis.saveDefaultConfig();とします。

saveDefaultConfig()の働き方は、config.ymlが存在しているか存在していないかでわけられます。もしconfig.ymlが存在しないときには、plugin/<プラグイン名>の中にjarファイルの中のconfig.ymlと同じものを作成し、config.ymlが存在するときには何もしません。つまり、上書きされることがない、ということです。

configデータを取り込む

マインクラフトのプラグインにはFileConfigurationという型が存在します。ここでは、configという名前の変数を作成します。御多分に洩れず、こんな型知らねーよと帰ってくるのでインポートしてやってください。この型に対してgetConfig()の戻り値を代入するとconfigデータを取得できます。2つ合わせてFileConfiguration config = getConfig()となります。

次に、一つ一つの項目の取り込みです。例を使って説明します。getConfigで取り込んだconfigファイルの中に、<設定名>: <設定内容(TRUE/FALSE)>というコンフィグファイルがあったとします。この時、<設定内容>を取得するには、config.getBoolean("設定名");を使うことで取得できます。このgetBooleanの部分は、<設定内容>の方によって変更可能です。ここではtrueとfalseの判別を付けるためにgetBoolean()を使用します。取得したコンフィグの内容を保存するために変数を一つ作りましょう。ここでは名前をconfig_testとします。変数の初期化と同時に取得するのであれば、boolean config_test = config.getBoolean("first");のようになります。

使用可能なget****()シリーズの紹介

get****()シリーズには多くの種類があって、getBoolean(String),getInt(String),getString(String)などの簡単に想像できるものから、階層でリストアップされているものすべてを取得するgetList(String),getStringList(String)のようなものもあります。

これでコンフィグデータを取り込むことはできました。あとはgetLoggerを使用してEnable時にtrueとfalse1を表示する処理を実装してみましょう。

trueかfalseかを取得して出力する

if(config_test){
    getLogger().info("TRUE");
}else{
    getLogger().info("FALSE");
}

ここまでのソースコードをまとめるとこうなります。

onEnableメソッドの中

this.saveDefaultConfig();

FileConfiguration config = getConfig();

boolean config_test = config.getBoolean("first");

if(config_test){
    getLogger().info("TRUE");
}else{
    getLogger().info("FALSE");
}

ここまでできたら、エクスポートして実行してみましょう。このときに、エクスポートするフォルダにconfig.ymlを含めてください。

エクスポートするフォルダ

図6.16: エクスポートするフォルダ

エクスポートできたら、サーバーを起動してください。サーバーログにTRUEと出てきましたか?

TRUEと出てくる

図6.17: TRUEと出てくる

確認したら、サーバーを一度止めて、plugin/firstpluginの中のconfig.ymlのfirst: truefirst: falseに書き換えてください。書き換えた後でサーバーを起動してください。すると...?

FALSEと出てくる

図6.18: FALSEと出てくる

先ほどと違ってfalseと出てきました。成功ですね。

6.8 おわりに

いかがだったでしょうか?僕は中3なのでこれが初めて部誌を書く経験になります。難しいですね...部誌書くの...。とても拙く不自由な日本語だったと自分でも思います。来年こそはもっとうまく書くぞ。

この部誌を読んでくれた人が一人でも「マインクラフトのプラグインを作ること」に興味を持ってくれるとうれしいです。