【Bukkit/Spigot】PlayerDeathEvent について

あるゲームを開発していて、この問題点に当たったのですが、

PlayerDeathEvent が発生した時点ではプレイヤーは死んでいません。

※正しくは違うのかもしれません。

PlayerDeathEvent が発生した後に死亡が確定しているように思います。

つまり、プレイヤーが死んだことをキーとするものを制作する場合、タイミングをずらして実行させる必要があります。

この問題のもっともわかりやすい例として、

Player#spigot().respawn

があります。

これを

@EventHandler
public void onDeath(PlayerDeathEvent e){
e.getEntity().spigot().respawn();
}

としてもうまくいきません。

正しくは

@EventHandler
public void onDeath(PlayerDeathEvent e){
new BukkitRunnable() {
e.getEntity().spigot().respawn();
}.runTaskLater(Plugin, 5L); // 20 で 1 秒
}

のようにしなければうまくいきません。

 

【Bukkit/Spigot】ランダムなプレイヤーを取得する方法

Java には最初から乱数を生成するためのクラス 

Random.class

がありますが、実はこれには欠点があり、同じシード(種)の場合には結果が同じになってしまいます。

しかし今回ご紹介する2通りの方法であれば、結果が同じになることはなく、完全にランダムな値を取得することが可能です。

1. シードを変える

一番手っ取り早い方法ですが、シードを変えることです。

シードを変えれば、結果が同じになることはありませんから。

Random rand = new Random(System.nanoTime());
List<Player> players = new ArrayList<Player>(Bukkit.getOnlinePlayers());
Player player = players.get(rand.nextInt(players.size());

2. シャッフル

結局プレイヤーを個別に取得するには一度配列にする必要があります。

その際に、配列にするのと同時にシャッフルしてしまおうという方法です。

List<Player> players = new ArrayList<Player>(Bukkit.getOnlinePlayers());
Collections.shuffle(players);
Player player = players.get(0);

 

【Minecraft】UHCを作ろう!#2

前回の続きとなるので前回を見てから読むことを推奨する。

前回はプラグインの基盤を作成したところで終わった。

今回は基本的なイベントリスナーを実装し、ワールドの自動生成を行うクラスを作成する。

イベントリスナーの実装

必要となるイベントを先に考えておこう。

・BlockBreak BlockPlace

・PlayerDeath

・PlayerJoin PlayerQuit

・EntityDamageByEntity

 

BlockBreak BlockPlace

ロビーでブロック設置、破壊を無効にする必要があると思うので実装する必要があります。

PlayerDeath

プレイヤーが死亡したときにヘッドをドロップする処理や、プレイヤーのキル数を加算する処理が必要なので実装します。

PlayerJoin PlayerQuit

プレイヤーがログインしたときにリセットしたり、スコアを取得する必要があるので実装します。

EntityDamageByEntity

PVPが無効の時にPVPができないようにする必要があるので実装します。

 

UHCの仕様を踏まえると、最低限以下のイベントも必要となりますね。

FoodLevelChange

体力ゲージの調整

EntityRegainHealth

資源回復の無効化

 

現時点で作成可能なイベントリスナーをさくっと作ってしまいましょう。

@EventHandler
public void onBreak(BlockBreakEvent e){
if (e.getPlayer().getWorld().getName().equals("lobby")){
e.setCancelled(true);
}
}
@EventHandler
public void onPlace(BlockPlaceEvent e){
if (e.getPlayer().getWorld().getName().equals("lobby")){
e.setCancelled(true);
}
}
@EventHandler
public void onFood(FoodLevelChangeEvent e){
e.setFoodLevel(e.getFoodLevel() - 1); // sorry no debug this code
}
@EventHandler
public void onRegain(EntityRegainHealthEvent e){
if (e.getRegainReason().equals(RegainReason.SATIATED)){
e.setCancelled(true);
}
}

 

次にワールドの自動生成用のクラスを作成します。

Bukkitでワールドを作成するには

WorldCreator というクラスを作成してそれを Bukkit#createWorld に渡す必要があります。

public World Create(String worldName){
WorldCreator wc = new WorldCreator(worldName);
// wc.seed(long) seed 値の設定などができます。
World w = Bukkit.createWorld(wc);
return w;
}

 

ワールドをアンロードするには

public void Unload(String worldName, boolean save){
Bukkit.unloadWorld(worldName, save);
}

です。とても分かりやすく簡単ですね。

 

このままでは、サーバーを再起動した際に、ワールドが読み込まれておらず手動で読み込む必要があるうえに読み込んだ際は前回作成したデータが引き継がれてしまいます(新規に作成されない)

そのため、生成する前に一度ワールドを削除する必要があります。

public void Remove(String worldName){
File f = new File(Bukkit.getWorldContainer(), worldName);
if (f.exists()){
IODelete(f);
}
}
public void IODelete(File file){
if (file.isFile()){
file.delete();
}else {
File[] files = file.listFiles();
for (File f : files){
IODelete(f);
}

file.delete();
}
}

 

「Remove();」を呼び出せば削除することができます(削除する前にワールドをアンロードする必要がありますよ!)

ここまでのコードを一つのクラスにまとめれば使いやすく便利なクラスの誕生です。

UHC以外でも使い道はありますね。