この記事は Minecraftサーバ開発・運営 Advent Calendar 2021 - 16日目の記事になります。
概要
Minecraft Java Editionのサーバーソフトウェアとして最も有名なSpigotで動作するプラグインで簡単な魔法を実装する記事になります。
設計
今回作るプラグイン 『MagicPole』 の設計はこんな感じで立ててみました。
・コマンドではなく専用のクラフトレシピを用意する
・魔法の詠唱には10秒のディレイを用意する
・魔法を使うにはマナ、もとい満腹度を要求する
・魔法を詠唱すると **HPブースト・スピード** が付くようにする
また要件はこんな感じ
- Minecraft 1.17.1で動作する
- カスタムレシピだが、テクスチャはなし。
では早速作っていきます
作成
今回は以下の環境で作成しました。 ★ デバッククライアント:Minecraft 1.17.1 ★ デバックサーバークライアント:paper 1.17.1 (MC:1.17.1) ★ 開発環境: JDK16 & IntelliJ IDEA 2021.3 (Ultimate Edition) (学生ライセンスでUltimate版を使っていますが、Community版でも特に変わりはないと思います。)
クラフトレシピの作成
最初のロジックをぱぱっと書いたら最初にカスタムレシピを作成します。
カスタムレシピとは作業台にプラグインとして実装した追加アイテムのレシピを追加するということです。プレイヤーは追加されたレシピを作業台に載せることで作成ができる ということです。
プラグインを読み込まれたときにレシピを対応させたいので onEnable()
メソッドに書いていきます。
カスタムアイテムの作成、アイテムメタデータの作成
カスタムアイテムとそのアイテムのメタデータを作成していきます。
ItemStack customItem = new ItemStack(Material.BLAZE_ROD, 1);
ItemMeta customMeta = customItem.getItemMeta();
if(customMeta == null) return;
customMeta.setDisplayName(ChatColor.LIGHT_PURPLE + "MagicPole");
アイテムには ブレイズロッドを選びました。 同時にnullチェックを行い、 customItem
に設定したいアイテム名を入力します。
次に説明(Lore
)を追加したいわけですが、 .setDisplayName()
のようにすると失敗します。
なぜなら説明をアイテムメタデータに設定するメソッド .setLore()
はただ .setDisplayName()
のようにString型ではなく、 List型を必要とします。. ただ普通に文字列を入れるだけではダメということですね。
List
を宣言し、変数にデータを格納し、その変数を .setLore()
に代入します。
List<String> customLore = new ArrayList<>();
customLore.add("魔法を使うことが出来る不思議なアイテムです。");
customLore.add("持って右クリックすることで詠唱し、HPブースト+スピード上昇の効果を受けることが出来ます。");
customLore.add("代償にあなたの空腹度を増やすようです.......");
customMeta.setDisplayName(ChatColor.LIGHT_PURPLE + "MagicPole");
customMeta.setLore(customLore);
最後にメタデータをセットして
customItem.setItemMeta(customMeta);
これで固有のメタデータを持つこのプラグイン限定のカスタムアイテムを作成できました。
カスタムレシピの追加
このアイテムを作業台で実際に作れるようにしたいのでカスタムレシピを追加します。
まずプラグインの名前空間に先程作った『MagicPole』のKeyを追加し、カスタムレシピで作れるようにしたいアイテム自身とKeyを登録します。
NamespacedKey customKey = new NamespacedKey(this, "magic_pole");
ShapedRecipe customRecipe = new ShapedRecipe(customKey, customItem);
次に肝心のレシピ自身を追加します。
Minecraftでアイテムをクラフトするにはこの3*3のスロットにアイテムを置く必要があります。プラグインでもこのアイテムの配列を書いてやる必要があります。
customRecipe.shape(
"R G",
" i ",
"i a"
);
このように実際にサバイバルでアイテムを配列するように各種Keyを書いていきます。
次に各種Keyにアイテムを割り当てていき、レシピを追加します。
customRecipe.setIngredient('R', Material.RABBIT); // 兎肉
customRecipe.setIngredient('G', Material.GOLD_BLOCK); // 金
customRecipe.setIngredient('i', Material.GOLD_INGOT); // 金
customRecipe.setIngredient('a', Material.APPLE); // りんご
Bukkit.addRecipe(customRecipe);
アイテムの処理を書く
このMagicPoleの処理を書いていきます。クラスを分けます。
package com.github.merunno.magicpole.Listener;
import com.github.merunno.magicpole.MagicPole;
public class ChantingListener {}
今回右クリックをすると詠唱を開始していきたいので PlayerInteractEvent
を使用します。これはプレイヤーが右クリックを行うと発火するイベントです。
右クリックしたアイテムの取得を行い、NullCheckをします。
またパーティクルなども使うのでプレイヤーのロケーションを取得もします。
Player player = e.getPlayer();
ItemStack item = e.getItem();
Location loc = player.getLocation();
if(item == null) return;
if(!item.hasItemMeta()) return;
そしてイベント発火時にアイテムの判定を行いますが、名前だけで判定すると木の棒などのブレイズロッド以外のアイテムに "MagicPole" という名前をつけるだけで使えるようになってしまいます。そのため item.getType()
でブレイズロッドかも判定します。
&&
をつけると複数の論理式で判定することが出来ます。
if(Objects.requireNonNull(item.getItemMeta()).getDisplayName().equalsIgnoreCase(ChatColor.LIGHT_PURPLE + "MagicPole") && item.getType() == Material.BLAZE_ROD) {}
- プレイヤーが右クリックをする
- その時のアイテムが
-
ChatColor.LIGHT_PURPLE + "MagicPole"
という名前である - ブレイズロッドである
-
この2つの論理式がどちらも true
だった場合は処理を実行します。
満腹度の処理をする
設計 をした際に 魔法を使うにはマナ、もとい満腹度を要求する と決めました。まずは満腹度の処理をします。
プレイヤーの満腹度は player.getFoodLevel();
(int) で取得できます。
if(foodlv <= 0) { // 満腹度が0以下だった場合は詠唱をキャンセル
player.sendMessage(ChatColor.RED + "お腹が空いて魔法を詠唱できない.........");
FoodError(loc);
return;
}
これでプレイヤーの満腹度が 0
(またはそれ以下) だった際はメッセージを返し、処理を終了します。
逆に 1
(またはそれ以上)だった場合の処理として満腹度を減少させます。
// 満腹度の処理 //
player.setFoodLevel(foodlv - 2);
魔法の処理を書く
まずは 設計 で決めた 魔法の詠唱には10秒のディレイを用意する 処理を書いていきます。
Spigotでディレイを実現する方法はいっぱいありますが、 new BukkitRunnable()
を今回は使っていきます。
player.sendMessage(ChatColor.AQUA + "詠唱を行った......10秒待機が必要みたいだ......");
new BukkitRunnable() {
@Override
public void run() {
player.sendMessage(ChatColor.YELLOW + "詠唱した。体力ブースト・スピードの効果を獲得。");
// 満腹度の処理 //
player.setFoodLevel(foodlv - 2);
// サウンド・パーティクル //
Chanting(loc);
for(int i = 0; i < 10; i++) {
ChantingSound(loc);
}
// エフェクト //
player.addPotionEffect(new PotionEffect(PotionEffectType.HEALTH_BOOST, 20*15, 1), true);
player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20*15, 1), true);
new BukkitRunnable() {
@Override
public void run() {
player.sendMessage(ChatColor.YELLOW + "エフェクト効果が切れたようです。");
}
}.runTaskLater(JavaPlugin.getPlugin(MagicPole.class), 20*15);
}
}.runTaskLater(JavaPlugin.getPlugin(MagicPole.class), 20*10);
満腹度の処理
サウンド・パーティクル
魔法の詠唱を行った際にパーティクルとサウンドを再生したいのでメソッドを作り、再生します。
private void FoodError(Location loc) { /* Sound: ミツバチの退出音 */
Objects.requireNonNull(loc.getWorld()).playSound(loc, Sound.BLOCK_BEEHIVE_EXIT, 2, 1);
}
private void ChantingSound(Location loc) { /* Sound: バブル */
Objects.requireNonNull(loc.getWorld()).playSound(loc, Sound.BLOCK_BUBBLE_COLUMN_BUBBLE_POP, 2, 1);
}
private void Chanting(Location loc) { /* Particle: ドラゴンブレス */
Objects.requireNonNull(loc.getWorld()).playEffect(loc, Effect.DRAGON_BREATH, 0, 10);
}
FoodError(loc);
ChantingSound(loc);
Chanting(loc);
各種、引数にプレイヤーのロケーションを渡し再生します。
また魔法を処理した際に音を連続で再生するためにfor文で繰り返し10回再生しようとしましたが、あまり効果はありませんでした。
付与するエフェクト
プレイヤーにエフェクトを付与する際は player.addPotionEffect(new PotionEffect(PotionEffectType.))
を使います。
引数 | 説明 |
---|---|
第一引数 | 付与するエフェクト |
第二引数 | 効果時間 |
第三引数 | 効果レベル |
第四引数 | 干渉する効果をかき消すか |
効果時間が終了した15秒後にメッセージを送りたいので new BukkitRunnable()
を使い処理を書きます。
new BukkitRunnable() {
@Override
public void run() {
player.sendMessage(ChatColor.YELLOW + "エフェクト効果が切れたようです。");
}
}.runTaskLater(JavaPlugin.getPlugin(MagicPole.class), 20*15);
完成
まとめ
Spigotは色々なことが出来、楽しいですね(Java自体も楽しい)
これからも色んなものを作っていきたいと思います。
明日:hmy2001さん|統合版パケット解析クライアントの話
それ以降はデスノートを作って運営にいたずらする(18日目 - Rark Hopperさん)、リア充を爆破するクリスマスツリーとプレゼントを作る(25日目 - yuko_fuyutsukiさん) と面白そうな記事が予定されています。
19日~23日が開いているので迷っている方はぜひ参加してみては~
Bye~