0. 初めに
build.gradle
や plugin.yml
の設定は終わらせた前提で進めます。
プロジェクトを作成する を参考にメインクラスを作るまで終わらせておいてください。
また、コマンドを作成して進めていきます。
詳しい説明は 2.2 コマンドを新しく作る でしていますので省略します。
1. plugin.yml にコマンドを登録する
commands:
dice:
usage: "/<command> [Max] [Count]"
description: "サイコロを振ります"
このようなコマンドを登録しておきます。
サイコロの目とサイコロの数を引数としています。
2. 準備する
メッセージに色をつけたいので、getColored
を用意しておきましょう。
object Util {
fun getColored(text: String): String {
return ChatColor.translateAlternateColorCodes('&', text)
}
}
3. コマンドの処理を書く
CommandExecutor
を継承した DiceCommand
というオブジェクトを作成します。
object DiceCommand: CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
}
}
さあ、引数の処理について考えてみましょう。
まず、引数が2つありますが、それぞれ1以上の整数です。
args
から取得できる値は文字列なので、整数に変換しなくてはいけません。
また、必ずしも整数に変換できるわけではありませんね。
エラーとなるのは次のような場合です。
・引数が入力されなかった場合
・引数を整数に変換できなかった場合
この二つを一度に処理できたら楽ですよね。
そのためにはこういう書き方をしてみましょう。
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
val max = args.getOrNull(0)?.toIntOrNull()
}
前回、args
の取得には []
を使いました。これを使うと、戻り値が null
になることは絶対ありません。
しかし、存在しない引数を取得しようとすると、例外というエラーが発生 します。
そのために、isNotEmpty
(size
) を使って条件分岐したのです。
逆に、getOrNull
を使うと、存在しない引数を取得した場合には戻り値が null
になります。
また、toIntOrNull
も同様です。toInt
というのもありますが、整数に変換できない場合は例外が発生 します。
toIntOrNull
を使うと 整数に変換できない場合は null
になります。
この2つから max
が null
だった時、
・引数が入力されなかった場合
・引数を整数に変換できなかった場合
のどちらかに当たるわけです。とても簡単に書くことができました。
では、null
だった場合のエラーを書きましょう。
val max = args.getOrNull(0)?.toIntOrNull()
if(max == null){
sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
return true
}
エラーを出したら、それ以降を処理する必要はありません。そのため、return true
をしておきましょう。
また、max
は1以上でなければなりません。それも続けて書いてしまいましょう。
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
val max = args.getOrNull(0)?.toIntOrNull()
if(max == null){
sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
return true
}
if(max < 1){
sender.sendMessage(getColored("&cサイコロの目は1以上を入力してください"))
return true
}
}
では、同様に count
も取得してみましょう。
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
val max = args.getOrNull(0)?.toIntOrNull()
if(max == null){
sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
return true
}
if(max < 1){
sender.sendMessage(getColored("&cサイコロの目は1以上を入力してください"))
return true
}
val count = args.getOrNull(1)?.toIntOrNull()
if(count == null){
sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
return true
}
if(count < 1){
sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
return true
}
}
ほとんど変わりませんね。
では次に、max
と count
を引数とした rollDice
関数を作ってみましょう。
戻り値は振ったサイコロの目全部です。これには List<Int>
というものが使えるでしょう。
まずは、こんな感じ。
private fun rollDice(max: Int, count: Int): List<Int> {
}
次に、結果を保存する変数を宣言しましょう。
private fun rollDice(max: Int, count: Int): List<Int> {
val result = mutableListOf<Int>()
return result
}
この間を書いていけばいいわけです。
「count
個 のサイコロを振る」というのを言い換えると、「count
回 サイコロを振る」となります。
これをプログラムで書くとこうなります。
private fun rollDice(max: Int, count: Int): List<Int> {
val result = mutableListOf<Int>()
for(i in 0 until count){
}
return result
}
内部的にはこのような処理になっています。
var i = 0
while (i < count){
i ++
}
i
を 0 で初期化し、i
が max
と等しくなるまで 加算され続けます。
要するに、count
回繰り返される のです。しかし、これより上の書き方が楽なのでそちらを使いましょう。
残りは、ランダムに値を得るだけです。
(1 .. max).random()
と書くと 1 以上 max
以下 の中でランダムな整数が取得できます。
Int .. Int
や Int until Int
は IntRange
となり、整数の範囲を表現することができます。
これを rollDice
内に実装しましょう。
private fun rollDice(max: Int, count: Int): List<Int> {
val result = mutableListOf<Int>()
for(i in 0 until count){
result.add((1 .. max).random())
}
return result
}
これで rollDice
は完成になりますが、List
について少し解説します。
List
と MutableList
というものがあります。
rollDice
の場合は、戻り値は List
で result
は MutableList
になっています。
どちらも値の一覧を保存できますが、MutableList
では 値の追加や削除 ができます。
逆に、List
ではそれが出来ません。使い分けは簡単です。
中身を操作したければ MutableList
を使えばいいのです。それ以外は List
を使います。
rollDice
の戻り値を自由に変更できる必要はありません。その為、List
で問題ありません。
しかし、result
が List
だと値の追加が出来ません。だから、MutableList
を使うのです。
では、onCommand
に実装しましょう。
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
val max = args.getOrNull(0)?.toIntOrNull()
if(max == null){
sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
return true
}
if(max < 1){
sender.sendMessage(getColored("&cサイコロの目は1以上を入力してください"))
return true
}
val count = args.getOrNull(1)?.toIntOrNull()
if(count == null){
sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
return true
}
if(count < 1){
sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
return true
}
val diceList = rollDice(max, count)
}
メッセージについて考えていきましょう。
[Dice] {名前} がサイコロを振りました {合計}({目の一覧})
こんなメッセージにしてみましょう。
val diceList = rollDice(max, count)
val result = "&6${diceList.sum()}&7(${diceList.joinToString()}"
diceList.sum()
で合計を取得することができます。
例えば [4, 2]
のようになっていたら 6
が戻り値になります。
diceList.joinToString()
で目の一覧を取得することができます。
例えば [4, 2]
のようになっていたら 4, 2
が戻り値になります。区切り文字はデフォルトで ", "
になっています。
次にメッセージの送信です。
実行したプレイヤーだけにメッセージを送っても味気ないので、周囲10ブロックにいるプレイヤーに送るとしましょう。
しかし、コンソールからだと周囲10ブロックのプレイヤーは取得できないので、サーバー全員に送りましょう。
val result = "&6${diceList.sum()}&7(${diceList.joinToString()})"
if(sender is Player){
// 範囲内のプレイヤーにメッセージを送る
} else {
// 全員にメッセージを送る
}
まずは、範囲内のプレイヤーにメッセージを送る方を考えてみましょう。
if(sender is Player){
val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
} else {
基本的には変わっていません。
では、範囲内のプレイヤーにメッセージを送る方法です。
if(sender is Player){
val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
if(entity is Player){
entity.sendMessage(message)
}
}
} else {
sender.getNearbyEntities(10.0, 10.0, 10.0)
で X, Y, Z
それぞれ10ブロック以内のエンティティを取得しています。
.forEach { entity -> }
で取得したエンティティ全ての処理を行っています。
if(entity is Player)
で entity
が Player
だった時のみ処理を行っています。
ただし、これだと sender
自身にはメッセージが送られません。
if(sender is Player){
val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
if(entity is Player){
entity.sendMessage(message)
}
}
sender.sendMessage(getColored("&b[Dice] &fサイコロを振りました $result"))
} else {
それ用のメッセージを書いて終わり。
次に、全員にメッセージを送る方を考えてみましょう。
} else {
val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
Bukkit.broadcastMessage(message)
}
最後に return true
を書いたら完成です。
} else {
val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
Bukkit.broadcastMessage(message)
}
return true
こんな感じになっていると思います。
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
val max = args.getOrNull(0)?.toIntOrNull()
if(max == null){
sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
return true
}
if(max < 1){
sender.sendMessage(getColored("&cサイコロの目は1以上を入力してください"))
return true
}
val count = args.getOrNull(1)?.toIntOrNull()
if(count == null){
sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
return true
}
if(count < 1){
sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
return true
}
val diceList = rollDice(max, count)
val result = "&6${diceList.sum()}&7(${diceList.joinToString()})"
if(sender is Player){
val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
if(entity is Player){
entity.sendMessage(message)
}
}
sender.sendMessage(getColored("&b[Dice] &fサイコロを振りました $result"))
} else {
val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
Bukkit.broadcastMessage(message)
}
return true
}
4. コマンドを登録する
class Main: JavaPlugin() {
override fun onEnable() {
registerCommand("dice", DiceCommand)
}
private fun registerCommand(label: String, executor: CommandExecutor){
val command = getCommand(label)
if(command != null){
command.setExecutor(executor)
logger.info("/$label を登録しました")
} else {
logger.severe("/$label を登録できませんでした")
}
}
}
では、サーバーでテストしましょう。
まずはプレイヤーが実行してみましょう。
次にコンソールから実行してみましょう。
このように表示されたら成功です。
表示範囲を引数のオプションとして追加したり、メッセージに振ったサイコロの情報を載せてみたりと、
たかがサイコロと思われるかもしれませんが、工夫できる点はたくさんあります。
今回作ったものを自分なりに改良してみてはいかがでしょうか。
お疲れ様でした。