石橋を叩いて壊すページ

インベントリ関連メソッドの動きをちょっと確認してみた

プレイヤーの所持品をインベントリ(Inventory)と言う。
プレイヤーの所持品をBukkitで確認したいなら、Player(正確にはHumanEntity。以後同様。)クラスのgetInventory()メソッドを実行し、
戻り値のInventoryオブジェクトを調べてみればいい。

プレイヤーに限らず、アイテムを所持するもの、たとえばチェスト・かまど・ホッパーなども、
同様に所持品を調べたければ各々のクラスのget…Inventory()メソッドでInventoryオブジェクトを取得すれば同様の操作ができる。
get…Inventory()とお茶を濁すような書き方をしたのは、クラスによって若干名称が違うためである。
たとえば、地面に設置されたチェスト(Chestクラス)では、getBlockInventory()という名前になる。

ちなみに、装備している防具の管理はちょっとややこしい。
モンスター(LivingEntity)にはgetEquipment()というメソッドがあり、防具の確認・設定ができるが、
戻り値のEntityEquipmentクラスはInventoryクラスの拡張クラスではないので、インベントリとは関係ない。

一方で、プレイヤー(Player)はgetInventory()を実行するとPlayerInventoryというInventoryクラスの拡張クラスのオブジェクトが返ってきて、
こちらはgetArmorContents()/setArmorContents(…)メソッドで防具を確認・変更できる。
ややこしいがこれはもう覚えるしかない話なので、詳しく考える必要もないだろう。

で、ここからが本題。
この複数パターンあるインベントリ周りの動作について調べてみた。

この記事の「防具」という単語は、その生物(LivingEntity)が現在装備している防具であり、
インベントリに入っていて装備はされていない状態のアイテムのことは含まない。
また、この記事の内容はあくまでgit-Bukkit-1.6.2-R1.0-b2879jnks (MC: 1.6.2)の動作を調べたもので、
時と場合により違う結果となる可能性があること、今後動作が変更される可能性があることに注意していただきたい。

PlayerInventoryのgetArmorContents()はどういう値を返すのか

戻り値はItemStack[]型である。それはAPIリファレンスを読めばわかるのだが、
じゃあ防具4つ(頭・胴・脚・足)を装備していたらどういう順番でアイテムがItemStackの配列に格納されるのか。
そんなことより、防具を装備していない部分にはnullが入るのか?配列の長さが短くなるのか?
防具をひとつも装備していないと戻り値自体がnullになりはしないだろうか。
もしnullが返る可能性があるとプログラマは戦慄するわけだが、その必要はあるのだろうか。

調べ方はこう。

@EventHandler
public void onPlayerInteractEvent(PlayerInteractEvent e) {

	ItemStack[] is=e.getPlayer().getInventory().getArmorContents();

	getServer().broadcastMessage("長さ"+is.length);
			
	for(int n=0;n!=is.length;n++)
	{
		getServer().broadcastMessage(is[n].toString());
	}
}

見てのとおり、ゲーム内でクリックするとgetArmorContents()を実行して内容を表示する。
じゃあ実際にやっていこう。


何も装備していない場合。
長さ4
ItemStack{AIR x 0}
ItemStack{AIR x 0}
ItemStack{AIR x 0}
ItemStack{AIR x 0}


頭だけ装備している場合。
長さ4
ItemStack{AIR x 0}
ItemStack{AIR x 0}
ItemStack{AIR x 0}
ItemStack{LEATHER_HELMET x 1}


全部装備している場合。
長さ4
ItemStack{LEATHER_BOOTS x 1}
ItemStack{IRON_LEGGINGS x 1}
ItemStack{DIAMOND_CHESTPLATE x 1}
ItemStack{GOLD_HELMET x 1}


全部装備している場合その2。
長さ4
ItemStack{GOLD_BOOTS x 1}
ItemStack{DIAMOND_LEGGINGS x 1}
ItemStack{IRON_CHESTPLATE x 1}
ItemStack{LEATHER_HELMET x 1}

どうやら戻り値は常に長さ4で、配列要素のどこにどの部位が割り当てられるかも決まっているようだ。
装備品の材質によって並び順が変わるといったこともない。
ちなみにAIRと書いてあるのはMaterial.AIRのことで、空気を装備しているという意味になる。

Bukkitはこのへんがややこしいんだよね。
「アイテムを装備していないのなら、装備品はnullに違いない!」とか先入観をもってると
if(所持アイテム==null){…装備していない場合の処理…}とかのコードを書いてしまって、if文の内側の処理が実行されなかったりする。
かと思えば、所持アイテムにnullを設定するとNullPointerExceptionが出るかというと、Material.Airが指定されたと看做してエラーなしとか
慣れればともかく慣れないと頭に?が浮かぶことしばしばある。

PlayerInventoryのsetArmorContents(ItemStack[])はどういう値を期待しているのか

プログラム書いてると大抵あるのが、setメソッドとgetメソッド。
値を設定したり参照したりするときに使う。setとgetあわせてアクセサと呼ばれるらしい。
マイクラもご他聞にもれず、getArmorContents()があるならsetArmorContents(…)がある。
get版が防具を参照するメソッドなのだから、set版は言うに及ばず装備品を設定するメソッドである。

上記したとおり、get版は常に長さ4の配列を返し、各要素の相当する部位も決まっている。
じゃあset版の引数を長さ4以外にしたらどうなるか。部位の順序を守らなかったら防具は設定されないのか?
既に装備していたら元のアイテムは消滅するのか?あとnullいれたらどうなるかついでに調べた。

調べる方法は先ほど同様、殴ると防具が設定されるようにしてある。
ちなみに、PlayerクラスのupdateInventory()メソッドは非推奨(Deprecated)なのだが、これに変わるメソッドがどこにもない。
(検索すると、これに相当するメソッドを追加しようとした気配はあるが、いまだ追加されていないといった感じか。)
updateInventory()を実行しないと、インベントリ内容の変更がクライアントに通知されず、
ゲームを一度ログアウトしログインしなおすまで防具や所持品が更新されないので、やむなく使っている。

@SuppressWarnings("deprecation")
@EventHandler
public void onPlayerInteractEvent(PlayerInteractEvent e) {

	ItemStack[] is={
			new ItemStack(Material.IRON_BOOTS),
			new ItemStack(Material.IRON_LEGGINGS),
			new ItemStack(Material.IRON_CHESTPLATE),
			new ItemStack(Material.IRON_HELMET)};

	e.getPlayer().getInventory().setArmorContents(is);
	
	e.getPlayer().updateInventory();
	
	getServer().broadcastMessage("装備完了");
}

さあやっていこう。特記ない限り、何も装備していない状態から上記コードを実行して結果を見た。


上記したコードのとおり、get版のメソッドにならい、長さ4の配列に
IRON_BOOTS,IRON_LEGGINGS,IRON_CHESTPLATE,IRON_HELMETの順にアイテムを設定した。
まったく問題なし。


上記したコードから配列の内容の順序をばらばらにし、
IRON_CHESTPLATE,IRON_HELMET,IRON_BOOTS,IRON_LEGGINGSの順にアイテムを設定した。
まったく問題なし。


上記したコードの配列の4番目を削除し、
IRON_BOOTS,IRON_LEGGINGS,IRON_CHESTPLATEの長さ3の配列とした。
まったく問題なし。


上記したコードの配列の2番目・3番目をnullに置き換え、
IRON_BOOTS,null,null,IRON_HELMETとした。
nullの部分はなにも装備されないようだ。問題なし。


上記したコードの配列の長さを2にし、
IRON_HELMET,IRON_HELMETと、同じアイテムを重ねて配置した。
ぜんぜんHELMETじゃないスロットにHELMETが割り当てられた。しかし材質を反映してか、鉄の脚と鉄の足が表示されている。


上記したコードの配列の長さを5にし、
IRON_HELMET,IRON_HELMET,IRON_HELMET,IRON_HELMET,IRON_HELMETと、同じアイテムを5つ重ねて配置した。
なにも装備されず、以下のエラーが出た。

Caused by: java.lang.ArrayIndexOutOfBoundsException: 4
        at net.minecraft.server.v1_6_R2.PlayerInventory.setItem(PlayerInventory.java:333)
        at org.bukkit.craftbukkit.v1_6_R2.inventory.CraftInventory.setItem(CraftInventory.java:82)
        at org.bukkit.craftbukkit.v1_6_R2.inventory.CraftInventoryPlayer.setArmorContents(CraftInventoryPlayer.java:97)
        at jp.jias.bukkit.TestPlugin.onPlayerInteractEvent(TestPlugin.java:46)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:425)
        ... 16 more


あらかじめ全身を金防具に包んだ状態で、上記したコードの配列の3番目と4番目を削除し、
IRON_BOOTS,IRON_LEGGINGSの長さ2の配列としたコードを実行した。
脚と足の装備が鉄に更新され、もともと装備していた金の足と金の脚はインベントリに入らず消滅した。


あらかじめ全身を金防具に包んだ状態で、上記したコードの配列の2番目をnullに、3番目をAIR(空気)に置き換え、
IRON_BOOTS,null,AIR,IRON_HELMETとした。
個人的にはnullは何もしない、AIRは装備を脱ぐと予想したが、そうでもないらしい。


上記したコードの配列の内容を書き換え、
WOOD(材木),APPLE(りんご),PAINTING(絵画),CAKE(ケーキ)にした。
まさかの反応が出た。

配列の長さを変えても順序をばらばらにしてもnullを入れても、わりと柔軟に対応してくれるようだ。
しかし、長さ4を超える配列を指定したり、同じ部位の装備を重複指定するといった無茶ぶりには対応してくれないようだ。
最後の実験は無茶ぶりを通り越して完全に悪意の部類だが、まさかエラーなしで通るとは思わなかった。

ということで、setArmorContents(…)を使うときは常識的な使用方法にとどめましょうということと、
装備を上書きすると消滅してしまうので、上書きの恐れがある場面ではきちんとアイテムを退避させましょうということが言える。

調べ足りないので次回ももうちょっとインベントリ周りを調べてみる。

この記事を評価

この記事にコメント

  1. ...

【この記事にコメント】
お名前:
コメント:

Menu