CraftBukkit・Spigotプラグインを作るときの小技のような何か

MinecraftのサーバMOD CraftBukkit・Spigotのプラグイン
1.7以前に作ったものなのでやり方が古いかも。
小技と言えないのもあるし数が少ないけど、何かしら役に立てば良いかなと思う(:3_ヽ)_
間違ってたり、もっと良い方法があったり、分からない部分があったらコメントに書くか[Twitter](https://twitter.com/rushuu_rw)にリプお願いします。

#・Tab押下時に表示されるリストの名前を変更
1番目から小技っぽくないけど。
[Player#setPlayerListName(nameString)](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/Player.html#setPlayerListName(java.lang.String))
チームや権限で色を分けるなどに使える。
名前を全く違う文字列にもできるけど、文字数の上限は16文字なので注意が必要。
17文字以上にすると例外が吐かれる。

※色を付ける場合、色の部分で2文字使われている。
`ChatColor.RED + "Name"`
　　↓
`"§cName"`

色コード  [Formatting codes](http://minecraft.gamepedia.com/Formatting_codes)

うちの1.8.8テスト環境だと何故か上限突破できる。
Javadocには16文字上限って書いてあるけど、どこかのバージョンで変更されたのかな・・・。
![ListNameChange.jpg](https://qiita-image-store.s3.amazonaws.com/0/22287/de4f56e2-7b94-13a3-0bae-d30ce69068f8.jpeg)

#・登録したイベントを最後に呼んでもらう
他のプラグインで、ブロックやアイテムの変更があるかもしれない時に、そのプラグインがイベントを処理してから、自分のプラグインの処理を実行したい場合など。

イベントリスナを[PluginManager#registerEvents()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/plugin/PluginManager.html#registerEvents(org.bukkit.event.Listener,%20org.bukkit.plugin.Plugin))するタイミングを遅らせるだけ。

>設定した[EventPriority](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/EventPriority.html)の中で最後になるだけなので、EventPriority.NORMAL設定でEventPriority.MONITORより後に呼ばれるわけではない。

色々方法はあるけど、とりあえずスケジューラのだけ載せておく。
初期化コマンドを作るとかで、任意のタイミングで実行できる方が確実かも。
[BukkitScheduler#scheduleSyncDelayedTask(plugin, task, delay)](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/scheduler/BukkitScheduler.html#scheduleSyncDelayedTask(org.bukkit.plugin.Plugin,%20java.lang.Runnable,%20long))

```java:useScheduler.java
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
	Bukkit.getServer().getPluginManager().registerEvents(plugin, listener);
}, 200);
```
3番目の引数はTick。

#・コマンド入力時のTabキー補完
これも小技ではない気がする。
`/test hoge`, `/test piyo`などのコマンドが用意されていたら
`/test`時点でTabキーを押すとチャット欄に`hoge, piyo`が表示され1番目が入力欄に入り、更にTabキーで表示されている候補を回すことができ、
`/test h`でTabキーを押すと入力欄に残りの`oge`が補完されるやつ。

[TabCompleter](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/command/TabCompleter.html)を実装したクラスで、onTabComplete()をオーバーライドして使う。
[JavaPlugin](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/plugin/java/JavaPlugin.html)には実装されているのでメインクラスにはすぐ書ける。
`JavaPlugin#getCommand(commandString).setExecutor(commandExecutor)`でコマンド別のクラスで処理する場合、[CommandExecutor](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/command/CommandExecutor.html)を実装しているそのクラスにTabCompleterを実装する。


上で書いた2つのコマンド補完

```java:tabComplete.java
public class Main extends JavaPlugin {
	@Override
	public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
		if (!command.getName().equalsIgnoreCase("test")) return super.onTabComplete(sender, command, alias, args);
		if (args.length == 1) {
			if (args[0].length() == 0) { // /testまで
				return Arrays.asList("hoge", "piyo");
			} else {
				//入力されている文字列と先頭一致
				if ("hoge".startsWith(args[0])) {
					return Collections.singletonList("hoge");
				} else if ("piyo".startsWith(args[0])) {
					return Collections.singletonList("piyo");
				}
			}
		}
		//JavaPlugin#onTabComplete()を呼び出す
		return super.onTabComplete(sender, command, alias, args);
	}
}

```


#・看板の文字を更新
看板はとても有用。
プレイヤーのログイン情報を出したり、看板をクリックで何か動作をしたり。
[Sign](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/Sign.html)
[SignChangeEvent](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/SignChangeEvent.html)
下側のイベントは例でPlayerInteractEventにしたけど、看板ブロックを取得できれば[World](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/World.html)から座標指定して直接取得しても良いと思う。


```java:signChange.java
@EventHandler
public void onSignChange(SignChangeEvent event) {
	String[] lines = event.getLines();//全行取得
	String line = event.getLine(0);//引数は[0-3]

	event.setLine(0, "0行目です");//行に文字列をセット

	//SignChangeEventではイベントに値をセットするのでupdate不要
}

@EventHandler //看板ブロックを右クリック
public void onSignClick(PlayerInteractEvent event) {
	if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
	Block clickedBlock = event.getClickedBlock();
	Material material = clickedBlock.getType();
	if (material == Material.SIGN_POST || material == Material.WALL_SIGN) {
		Sign sign = (Sign) clickedBlock.getState();

		String[] lines = sign.getLines();//全行取得
		String line = sign.getLine(0);//引数は[0-3]

		sign.setLine(0, "0行目です");//行に文字列をセット

		sign.update(); //Signを直接変更する場合はこれで更新完了
		//updateを忘れると反映されないので注意
	}
}
```

##応用?
[PlayerItemHeldEvent](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/player/PlayerItemHeldEvent.html)でプレイヤーがスクロール操作した時のスロット(何て名称か分からない)の選択変更を検知できる。(数字キーでも呼ばれる)
このイベントから変更前・変更後のスロット番号を取得できるので、プレイヤーが上下どちらにスクロールしたかを判定して、上下への選択項目変更などに使える。

スクロール検知は看板以外でも使えるけど、看板変更の応用なのでそのコードを載せておく(:3_ヽ)_

・プレイヤーが看板に向かって、カーソル(画面中央の十字)を合わせているときにスクロールすると、看板の文字が変化する。
・設置してある看板には既に文字列が入ってる状態
`ChatColor.GREEN + "Item 0"`
`"Item 1"`
`"Item 2"`
`"Item 3"`


```java:scrollSign.java
private int currentLine = 0;

@EventHandler
public void onScrolling(PlayerItemHeldEvent event){
	//プレイヤーの向いている方向にあるブロックを取得
	Block block = event.getPlayer().getTargetBlock((Set<Material>) null, 5);
	Material material = block.getType();
	if (material == Material.SIGN_POST || material == Material.WALL_SIGN) {
		Sign sign = (Sign) block.getState();
		//変更後、変更前のスロット番号取得
		//手持ちスロットの番号は[0-8]
		int slotIndex = event.getNewSlot() - event.getPreviousSlot();
		if(slotIndex == -1 || slotIndex == 8){
			//up
			selectLine(sign, true);
		}else if(slotIndex == 1 || slotIndex == -8){
			//down
			selectLine(sign, false);
		}
	}
}

private void selectLine(Sign sign, boolean scrollUp) {
	if (scrollUp && currentLine == 0) {//0行目状態で上スクロール取消
		return;
	}
	if (!scrollUp && currentLine == 3) {//3行目状態で下スクロール取消
		return;
	}
	String line = sign.getLine(currentLine);
	line = line.substring(2);//先頭の色コード削除
	sign.setLine(currentLine, line);
	if (scrollUp) {
		currentLine--;
	} else {
		currentLine++;
	}
	line = sign.getLine(currentLine);
	line = ChatColor.GREEN + line;//先頭に色コード追加
	sign.setLine(currentLine, line);
	sign.update();
}
```
ある程度見やすいように書いたつもりだけど、分かりにくかったらごめんなさい。
![scrollSign.jpg](https://qiita-image-store.s3.amazonaws.com/0/22287/17972697-fcd7-d98f-ee3c-8a4db7578494.jpeg)
画像は「Item 0」の行が選択された状態で、スロット[1]からスロット[2]に変更したもの。
横の穴は気にしないで。

あとはこれを使って色々機能を作る感じ。
「看板右クリックしたら、選択されてる行に書いてるアイテムを出す」とか。

#・簡易カスタムアイテム
アイテムの名前を変更したり、説明文を追加したり。
[ItemStack#getItemMeta()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/ItemStack.html#getItemMeta()) で取得できる[ItemMeta](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/meta/ItemMeta.html)からアイテム名などを変更できる。

```java:itemMetaChange.java
ItemStack itemStack = new ItemStack(Material.BREAD);
ItemMeta itemMeta = itemStack.getItemMeta();
//アイテム名をセット
itemMeta.setDisplayName("焼きたてのパン");
List<String> lore = Arrays.asList(ChatColor.RESET + "とても良い香りがする", ChatColor.RESET + "120円");
//説明文をセット
itemMeta.setLore(lore);
//ItemMetaをセットしないと反映されない。
itemStack.setItemMeta(itemMeta);
```
説明文の所は文字列の中で改行ではなく、Listに要素を追加することで改行することができる。
文字列の先頭に`ChatColor.RESET`を入れ無い場合、画像の下側のように先頭に斜体と紫色のコードが入る。
DisplayNameは先頭に斜体のみ
Loreは先頭に斜体と紫色のコード(ChatColor.DARK_PURPLE)
![2015-12-03_23.32.48.png](https://qiita-image-store.s3.amazonaws.com/0/22287/45ecda86-d54e-8465-279d-d69e20464633.png)


#・インベントリ作成
鞄・バックパックなど追加収納を作りたい時に。
[Server#createInventory()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Server.html#createInventory(org.bukkit.inventory.InventoryHolder,%20int,%20java.lang.String))
`Inventory inventory = Bukkit.getServer().createInventory(null, 9 * row, "バックパック")`
2番目の引数は必ず9の倍数。
このメソッドで作ると[InventoryType](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryType.html)はCHESTで作られる。
新しく作ったインベントリをプレイヤーに開かせるには、[HumanEntity#openInventory()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/HumanEntity.html#openInventory(org.bukkit.inventory.Inventory))に作ったインベントリを渡す。
渡すとすぐ開かれるので注意。


画像は2番目の引数を27で実行したので3行だが、9を渡せば1行にもできる。
![createInventory.jpg](https://qiita-image-store.s3.amazonaws.com/0/22287/7dcb90b4-bdda-9af8-2afa-08800fbfa4d1.jpeg)
**※注意**
作ったインベントリはサーバに保存ません。
インベントリ名、スロット数、中身などの保存は自分で実装する必要があります。
[BukkitObjectOutputStream](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/util/io/BukkitObjectOutputStream.html)と本体にあるBase64Coderでシリアライズするのが一般的のはず・・・。


##応用
収納以外で使ってみる。
アイテム名を変えたアイテムを置き、それをクリックすることで何かを動作させるUIに。
新しく作ったインベントリでも[InventoryClickEvent](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryClickEvent.html)が呼ばれるので、そこでイベントをキャンセルしてクリックされたアイテムを判定することで実装できる。(要インベントリ整理MOD対処)
InventoryClickEvent＃getSlotType()が[SlotType](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryType.SlotType.html).OUTSIDE(グレーアウトしてる部分)だったら次のページへ移動なども。
イベントが呼ばれた時に、アイテムをカーソルが掴んでいる常態か、スロットにある状態か迷いやすいので注意。
[InventoryClickEvent#getCursor()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryClickEvent.html#getCursor())
[InventoryClickEvent#getCurrentItem()](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/inventory/InventoryClickEvent.html#getCurrentItem())


#(:3_ヽ)_
昔のソースを漁って「使えそうなの6個だけか・・・10個くらいあればな」と思ってたけど、意外と長くなってしまった。
役に立ったのがあればいいな。